diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0af7fcd0..db42e215 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -47,7 +47,7 @@ jobs: VITE_HASH_HISTORY: true run: | chmod +x build.sh && ./build.sh -Wpz - npx vite build --base=/hyperquark/$BRANCH_NAME/ + npm run build -- --base=/hyperquark/$BRANCH_NAME/ - name: Move files to tmp run: mv ./playground/dist /tmp/hq-dist diff --git a/Cargo.toml b/Cargo.toml index 68b3f529..01232fe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,14 @@ publish = false [dependencies] serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } -serde_json = { version = "1.0.145", default-features = false, features = ["alloc"] } +serde_json = { version = "1.0.149", default-features = false, features = ["alloc"] } enum-field-getter = { path = "enum-field-getter" } -wasm-encoder = "0.243.0" -indexmap = { version = "2.11.4", default-features = false } +wasm-encoder = "0.245.1" +indexmap = { version = "2.13.0", default-features = false } hashers = "1.0.1" -uuid = { version = "1.18.1", default-features = false, features = ["v4", "js"] } -regex = "1.11.2" -lazy-regex = "3.2.0" +uuid = { version = "1.23.0", default-features = false, features = ["v4", "js"] } +regex = "1.12.3" +lazy-regex = "3.6.0" bitmask-enum = "2.2.5" itertools = { version = "0.14.0", default-features = false, features = ["use_alloc"] } wasm-bindgen = "0.2.103" @@ -22,10 +22,8 @@ wasm-gen = { path = "wasm-gen", version = "0.2.0" } petgraph = { version = "0.8.1", default-features = false, features = ["stable_graph"] } [dev-dependencies] -# wasmparser = "0.243.0" -wasmparser = { git = "https://github.com/pufferfish101007/wasm-tools.git", rev = "92357c8" } -wasmprinter = "0.243.0" -#reqwest = { version = "0.11", features = ["blocking"] } +wasmparser = { git = "https://github.com/pufferfish101007/wasm-tools.git", rev = "4e9ffc0" } +wasmprinter = "0.245.1" [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] # ezno-checker = { git = "https://github.com/kaleidawave/ezno.git", rev = "96d5058bdbb0cde924be008ca1e5a67fe39f46b9" } diff --git a/build.rs b/build.rs index 606f4688..3dceebd7 100644 --- a/build.rs +++ b/build.rs @@ -156,7 +156,6 @@ impl IrOpcode {{ &self, inputs: Rc<[crate::ir::Type]>, ) -> HQResult {{ - // crate::log!("{{:}}", self); if inputs.iter().any(crate::ir::Type::is_none) {{ hq_bug!("got none input type :scream: \n at opcode {{:}}", self) }} diff --git a/js/looks/switchbackdropto.ts b/js/looks/switchbackdropto.ts index 76b13d52..3a236c8a 100644 --- a/js/looks/switchbackdropto.ts +++ b/js/looks/switchbackdropto.ts @@ -1,8 +1,6 @@ import { renderer, costumes, target_skins, stageIndex } from "../shared"; export function switchbackdropto(costume_num: number) { - console.log("switch backdrop to", costume_num); - console.log(stageIndex(), costumes()); const costume = costumes()[stageIndex()][costume_num]; if (typeof costume === "undefined") return; renderer().getSkin(target_skins()[stageIndex()][0]).setSVG(costume.data); diff --git a/js/shared.ts b/js/shared.ts index 9e5dc1ca..cd7aa0f1 100644 --- a/js/shared.ts +++ b/js/shared.ts @@ -52,7 +52,6 @@ export function setup( ) { _target_names = target_names; _target_bubbles = _target_names.map((_) => null); - console.log(_target_names, _target_bubbles); _renderer = renderer; _pen_skin = pen_skin; _target_skins = target_skins; diff --git a/package-lock.json b/package-lock.json index 6d176c09..2656e3b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hyperquark", - "version": "0.0.8", + "version": "0.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hyperquark", - "version": "0.0.8", + "version": "0.0.9", "dependencies": { "@scratch/scratch-vm": "^12.3.0", "binaryen": "github:pufferfish101007/binaryen.js#non-nullable-table", @@ -14,38 +14,113 @@ "pinia-plugin-persistedstate": "^4.7.1", "scratch-parser": "5.1.1", "scratch-render": "github:hyperquark/scratch-render#develop", - "scratch-sb1-converter": "2.0.228", - "vite-plugin-wasm": "3.5.0", - "vue": "3.5.21", - "vue-router": "4.5.1", + "scratch-sb1-converter": "1.0.324", + "vue": "3.5.31", + "vue-router": "5.0.4", "wasm-feature-detect": "1.8.0" }, "devDependencies": { - "@vitejs/plugin-vue": "6.0.1", - "prettier": "^3.7.4", - "vite": "7.3.0", - "vite-plugin-node-polyfills": "0.24.0", - "vitest": "^4.0.16" - } + "@vitejs/plugin-vue": "6.0.5", + "prettier": "^3.8.1", + "vite": "8.0.3", + "vite-plugin-node-polyfills": "0.26.0", + "vite-plugin-wasm": "3.6.0", + "vitest": "^4.1.2" + } + }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "license": "MIT" }, "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.1.tgz", + "integrity": "sha512-iGWN8E45Ws0XWx3D44Q1t6vX2LqhCKcwfmwBYCDsFrYFS6m4q/Ks61L2veETaLv+ckDC6+dTETJoaAAb7VjLiw==", "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", @@ -66,12 +141,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -81,18 +156,18 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -102,10 +177,41 @@ "node": ">=6.9.0" } }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@bramus/specificity/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/@bramus/specificity/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "funding": [ { "type": "github", @@ -118,13 +224,13 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", "funding": [ { "type": "github", @@ -137,17 +243,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", "funding": [ { "type": "github", @@ -160,21 +266,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "funding": [ { "type": "github", @@ -187,16 +293,16 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "funding": [ { "type": "github", @@ -209,7 +315,44 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -219,11 +362,13 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -235,11 +380,13 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -251,11 +398,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -267,11 +416,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -283,11 +434,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -299,11 +452,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -315,11 +470,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -331,11 +488,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -347,11 +506,13 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -363,11 +524,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -379,11 +542,13 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -395,11 +560,13 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -411,11 +578,13 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -427,11 +596,13 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -443,11 +614,13 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -459,11 +632,13 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -475,11 +650,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -491,11 +668,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -507,11 +686,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -523,11 +704,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -539,11 +722,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -555,11 +740,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -571,11 +758,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -587,11 +776,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -603,11 +794,13 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -619,34 +812,59 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", - "optional": true, - "peer": true, "engines": { "node": ">=6.0.0" } @@ -655,6 +873,7 @@ "version": "0.3.11", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, "license": "MIT", "optional": true, "peer": true, @@ -674,17 +893,299 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.29", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", - "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", "dev": true, "license": "MIT" }, @@ -741,11 +1242,13 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-android-arm64": { "version": "4.46.4", @@ -754,11 +1257,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.46.4", @@ -767,11 +1272,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-x64": { "version": "4.46.4", @@ -780,11 +1287,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-arm64": { "version": "4.46.4", @@ -793,11 +1302,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-x64": { "version": "4.46.4", @@ -806,11 +1317,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { "version": "4.46.4", @@ -819,11 +1332,13 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { "version": "4.46.4", @@ -832,11 +1347,13 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { "version": "4.46.4", @@ -845,11 +1362,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.46.4", @@ -858,11 +1377,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { "version": "4.46.4", @@ -871,11 +1392,13 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { "version": "4.46.4", @@ -884,11 +1407,13 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { "version": "4.46.4", @@ -897,11 +1422,13 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-musl": { "version": "4.46.4", @@ -910,11 +1437,13 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.46.4", @@ -923,11 +1452,13 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.46.4", @@ -936,11 +1467,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.46.4", @@ -949,11 +1482,13 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.46.4", @@ -962,11 +1497,13 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-ia32-msvc": { "version": "4.46.4", @@ -975,11 +1512,13 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.46.4", @@ -988,19 +1527,21 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@scratch/scratch-render": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@scratch/scratch-render/-/scratch-render-12.3.0.tgz", - "integrity": "sha512-KAN/Pmy4ZfYuLt/I9Rg9V80crct518YRMV8MP5nghGK6nGupx4Fl9XXKcSGZyBC3erIJbzkotsValZ1HxQR/Yw==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@scratch/scratch-render/-/scratch-render-12.7.0.tgz", + "integrity": "sha512-pzux9y8vMvyzyIYpFfe6zKkesKgdIS/oNf6JzCkwh2Qz9PQ/rEC4qx6o2ryLH86hKXkN+0lbdRoTFBmhTo4gOQ==", "license": "AGPL-3.0-only", "dependencies": { - "@scratch/scratch-svg-renderer": "12.3.0", + "@scratch/scratch-svg-renderer": "12.7.0", "grapheme-breaker": "0.3.2", "hull.js": "0.2.10", "ify-loader": "1.1.0", @@ -1014,16 +1555,16 @@ } }, "node_modules/@scratch/scratch-svg-renderer": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@scratch/scratch-svg-renderer/-/scratch-svg-renderer-12.3.0.tgz", - "integrity": "sha512-8yxaUYzDAy4/pk8NgIM5KVuvwv+Rzfy8Fd2SQ47PhG8DQMfzW9M0JqPW9AnZMAJOfP8lEQkmSMGRSQtOARETlA==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@scratch/scratch-svg-renderer/-/scratch-svg-renderer-12.7.0.tgz", + "integrity": "sha512-zS9oBaD8Bb9ZH9bGb+2am3IwglM/VxvtCW+/w0uBox+hoJNOfInmQHTw83UTu3iYKKzKUy3prcAQgwM81waA5Q==", "license": "AGPL-3.0-only", "dependencies": { "base64-js": "1.5.1", "base64-loader": "1.0.0", "css-tree": "1.1.3", "fastestsmallesttextencoderdecoder": "1.0.22", - "isomorphic-dompurify": "2.26.0", + "isomorphic-dompurify": "2.36.0", "transformation-matrix": "1.15.3", "tslog": "4.10.2" }, @@ -1032,13 +1573,13 @@ } }, "node_modules/@scratch/scratch-vm": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/@scratch/scratch-vm/-/scratch-vm-12.3.0.tgz", - "integrity": "sha512-zOqessaWDq77u3rTz30d9OJkQV6goeRV+ZhGDMJlMuolACEMz7qbBsX6Pl+p5+MIkJlVKNAmQ4O7erzc99ZjHg==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@scratch/scratch-vm/-/scratch-vm-12.7.0.tgz", + "integrity": "sha512-c4lZDMdBHvqIK1EXjkVvN5DGlCZgU24x8e52KHz3E5tgp9YS4AK/XpB8KJKLfb12naa9knASz1vO/5MkdsYjZA==", "license": "AGPL-3.0-only", "dependencies": { - "@scratch/scratch-render": "12.3.0", - "@scratch/scratch-svg-renderer": "12.3.0", + "@scratch/scratch-render": "12.7.0", + "@scratch/scratch-svg-renderer": "12.7.0", "@vernier/godirect": "1.8.3", "arraybuffer-loader": "1.0.8", "atob": "2.1.2", @@ -1053,12 +1594,11 @@ "scratch-audio": "2.0.268", "scratch-parser": "6.0.0", "scratch-sb1-converter": "2.0.279", - "scratch-storage": "5.0.10", + "scratch-storage": "6.1.8", "scratch-translate-extension-languages": "1.0.7", "text-encoding": "0.7.0", "tslog": "4.10.2", - "uuid": "8.3.2", - "web-worker": "1.3.0" + "uuid": "8.3.2" } }, "node_modules/@scratch/scratch-vm/node_modules/jszip": { @@ -1107,6 +1647,12 @@ "text-encoding": "^0.7.0" } }, + "node_modules/@scratch/task-herder": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@scratch/task-herder/-/task-herder-12.6.2.tgz", + "integrity": "sha512-x3R/+ttXOqKCfS25YVkuyhFX5CZ1+wTNDELsYhvmayXcOhuQwZFTnQWt2RtrYzTJnZ6iNVT6KgvQyEIyTKBmUQ==", + "license": "AGPL-3.0-only" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -1160,6 +1706,17 @@ "integrity": "sha512-808EqPQbmUD6/IMpWUXLOZcblCHf9xaiB+un0RYNNE9+6VRjoiw6Be8R32tZ0ips1PX/15tlnA2Ev4UUgg827Q==", "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -1182,6 +1739,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/trusted-types": { @@ -1198,48 +1756,48 @@ "license": "BSD-3-Clause" }, "node_modules/@vitejs/plugin-vue": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", - "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz", + "integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-beta.29" + "@rolldown/pluginutils": "1.0.0-rc.2" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "node_modules/@vitest/expect": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", - "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", + "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.0.0", + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", - "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", + "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.16", + "@vitest/spy": "4.1.2", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1248,7 +1806,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -1270,26 +1828,26 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", - "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", + "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", - "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", + "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.16", + "@vitest/utils": "4.1.2", "pathe": "^2.0.3" }, "funding": { @@ -1297,13 +1855,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", - "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", + "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", + "@vitest/pretty-format": "4.1.2", + "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1312,9 +1871,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", - "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", + "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", "dev": true, "license": "MIT", "funding": { @@ -1322,73 +1881,128 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", - "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", + "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.16", - "tinyrainbow": "^3.0.3" + "@vitest/pretty-format": "4.1.2", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@vue-macros/common": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.2.tgz", + "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, "node_modules/@vue/compiler-core": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz", - "integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.31.tgz", + "integrity": "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@vue/shared": "3.5.21", - "entities": "^4.5.0", + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.31", + "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz", - "integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.31.tgz", + "integrity": "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.21", - "@vue/shared": "3.5.21" + "@vue/compiler-core": "3.5.31", + "@vue/shared": "3.5.31" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz", - "integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.31.tgz", + "integrity": "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@vue/compiler-core": "3.5.21", - "@vue/compiler-dom": "3.5.21", - "@vue/compiler-ssr": "3.5.21", - "@vue/shared": "3.5.21", + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.31", + "@vue/compiler-dom": "3.5.31", + "@vue/compiler-ssr": "3.5.31", + "@vue/shared": "3.5.31", "estree-walker": "^2.0.2", - "magic-string": "^0.30.18", - "postcss": "^8.5.6", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz", - "integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.31.tgz", + "integrity": "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.21", - "@vue/shared": "3.5.21" + "@vue/compiler-dom": "3.5.31", + "@vue/shared": "3.5.31" } }, "node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.1.tgz", + "integrity": "sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.1.1" + } + }, + "node_modules/@vue/devtools-api/node_modules/@vue/devtools-kit": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.1.tgz", + "integrity": "sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.1.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-api/node_modules/@vue/devtools-shared": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.1.tgz", + "integrity": "sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ==", + "license": "MIT" + }, + "node_modules/@vue/devtools-api/node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "license": "MIT" }, "node_modules/@vue/devtools-kit": { @@ -1416,53 +2030,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.21.tgz", - "integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.31.tgz", + "integrity": "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.21" + "@vue/shared": "3.5.31" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.21.tgz", - "integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.31.tgz", + "integrity": "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.21", - "@vue/shared": "3.5.21" + "@vue/reactivity": "3.5.31", + "@vue/shared": "3.5.31" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz", - "integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.31.tgz", + "integrity": "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.21", - "@vue/runtime-core": "3.5.21", - "@vue/shared": "3.5.21", - "csstype": "^3.1.3" + "@vue/reactivity": "3.5.31", + "@vue/runtime-core": "3.5.31", + "@vue/shared": "3.5.31", + "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.21.tgz", - "integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.31.tgz", + "integrity": "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.21", - "@vue/shared": "3.5.21" + "@vue/compiler-ssr": "3.5.31", + "@vue/shared": "3.5.31" }, "peerDependencies": { - "vue": "3.5.21" + "vue": "3.5.31" } }, "node_modules/@vue/shared": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.21.tgz", - "integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.31.tgz", + "integrity": "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==", "license": "MIT" }, "node_modules/@webassemblyjs/ast": { @@ -1854,6 +2468,38 @@ "node": ">=0.10.0" } }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.3.tgz", + "integrity": "sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.4", + "ast-kit": "^2.1.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/async-each": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", @@ -1966,6 +2612,15 @@ "integrity": "sha512-p32+F8dg+ANGx7s8QsZS74ZPHfIycmC2yZcoerzFgbersIYWitPbbF39G6SBx3gyvzyLH5nt1ooocxr0IHuWKA==", "license": "MIT" }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2418,9 +3073,9 @@ "license": "MIT" }, "node_modules/chai": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", - "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { @@ -2667,6 +3322,12 @@ "typedarray": "^0.0.6" } }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, "node_modules/console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -2678,6 +3339,13 @@ "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/copy-anything": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", @@ -2782,15 +3450,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cross-fetch": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", - "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, "node_modules/crypto-browserify": { "version": "3.12.1", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", @@ -2831,18 +3490,72 @@ } }, "node_modules/cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz", + "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==", "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" + "@asamuzakjp/css-color": "^5.0.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.28", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.6" }, "engines": { - "node": ">=18" + "node": ">=20" + } + }, + "node_modules/cssstyle/node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", + "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/cssstyle/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, + "node_modules/cssstyle/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2857,16 +3570,16 @@ "peer": true }, "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/debug": { @@ -2978,6 +3691,16 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/diff-match-patch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", @@ -3061,9 +3784,9 @@ } }, "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -3194,9 +3917,9 @@ } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -3237,9 +3960,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, @@ -3265,8 +3988,11 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, "hasInstallScript": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -3513,6 +4239,12 @@ "node": ">=12.0.0" } }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "license": "MIT" + }, "node_modules/extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -4255,15 +4987,15 @@ "license": "MIT" }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/htmlparser2": { @@ -4339,18 +5071,6 @@ "deprecated": "This package is unmaintained and vulnerable. Do not use it.", "license": "BSD" }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4749,16 +5469,16 @@ } }, "node_modules/isomorphic-dompurify": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.26.0.tgz", - "integrity": "sha512-nZmoK4wKdzPs5USq4JHBiimjdKSVAOm2T1KyDoadtMPNXYHxiENd19ou4iU/V4juFM6LVgYQnpxCYmxqNP4Obw==", + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.36.0.tgz", + "integrity": "sha512-E8YkGyPY3a/U5s0WOoc8Ok+3SWL/33yn2IHCoxCFLBUUPVy9WGa++akJZFxQCcJIhI+UvYhbrbnTIFQkHKZbgA==", "license": "MIT", "dependencies": { - "dompurify": "^3.2.6", - "jsdom": "^26.1.0" + "dompurify": "^3.3.1", + "jsdom": "^28.0.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.5" } }, "node_modules/isomorphic-timers-promises": { @@ -4778,34 +5498,35 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", - "license": "MIT", - "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.5.0", - "html-encoding-sniffer": "^4.0.0", + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", + "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -4816,6 +5537,18 @@ } } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -4912,6 +5645,267 @@ "immediate": "~3.0.5" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/linebreak": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-0.3.0.tgz", @@ -4956,6 +5950,23 @@ "node": ">=4.0.0" } }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4997,6 +6008,21 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -5245,6 +6271,47 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -5267,6 +6334,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, "node_modules/multipipe": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.3.1.tgz", @@ -5332,48 +6405,6 @@ "license": "MIT", "peer": true }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -5603,12 +6634,6 @@ "node": ">=0.10.0" } }, - "node_modules/nwsapi": { - "version": "2.2.23", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", - "license": "MIT" - }, "node_modules/object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -5889,9 +6914,9 @@ "license": "MIT" }, "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -5966,7 +6991,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/pbkdf2": { @@ -6050,9 +7074,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -6138,6 +7162,17 @@ "node": ">=10" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -6158,9 +7193,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "funding": [ { "type": "opencollective", @@ -6194,9 +7229,9 @@ } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -6316,6 +7351,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", @@ -6472,6 +7523,15 @@ "node": ">=0.10" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -6540,11 +7600,55 @@ "inherits": "^2.0.1" } }, + "node_modules/rolldown": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.12" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.46.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.4.tgz", "integrity": "sha512-YbxoxvoqNg9zAmw4+vzh1FkGAiZRK+LhnSrbSrSXMdZYsRPDWoshcSd/pldKRO6lWzv/e9TiJAVQyirYIeSIPQ==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -6579,12 +7683,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "license": "MIT" - }, "node_modules/run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -6629,12 +7727,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -6794,10 +7886,10 @@ "license": "MIT" }, "node_modules/scratch-sb1-converter": { - "version": "2.0.228", - "resolved": "https://registry.npmjs.org/scratch-sb1-converter/-/scratch-sb1-converter-2.0.228.tgz", - "integrity": "sha512-/XjJgvcVweBp+cFbgpUz1ctLMXZ17fVLQOioXGmHIoO4OyGi8hEhigQAEUZ3wpyQwvAsSWNRvx1qpO3hCQNDUQ==", - "license": "AGPL-3.0-only", + "version": "1.0.324", + "resolved": "https://registry.npmjs.org/scratch-sb1-converter/-/scratch-sb1-converter-1.0.324.tgz", + "integrity": "sha512-ZHIf2KCmYKchayLQFknni7QfXhOCI+w4Wq1wIQuN5D6xZwUyIe51/B4YO+pofOGr3tz4nnwQIjgs3AowdNKWDQ==", + "license": "BSD-3-Clause", "dependencies": { "js-md5": "^0.7.3", "minilog": "^3.1.0", @@ -6805,16 +7897,16 @@ } }, "node_modules/scratch-storage": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-5.0.10.tgz", - "integrity": "sha512-qtBjqFRYWqpnD+pPanVMadPielmNLZH4zCSuz/cnzKFY1RPJXjigMMP8vgm/XvM4zn01tUyl4z/OliWRB9hxbw==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-6.1.8.tgz", + "integrity": "sha512-uDkSNIY7yLalsynLmcGRs6TfF9C9UlCw+OYP0Tr++gjuSJTjOMm/Ve6y5kpxtjYWOAxgzOQgu+QcNz07vELXHg==", "license": "AGPL-3.0-only", "dependencies": { "@babel/runtime": "^7.21.0", + "@scratch/task-herder": "12.6.2", "arraybuffer-loader": "^1.0.3", "base64-js": "^1.3.0", "buffer": "6.0.3", - "cross-fetch": "^4.1.0", "fastestsmallesttextencoderdecoder": "^1.0.7", "js-md5": "^0.7.3", "minilog": "^3.1.0" @@ -6850,6 +7942,12 @@ "integrity": "sha512-6+bQU9iVYv23T8J0SjpV6MTugm0y8myh/4DPgu1BGfccysdkaWzu3MkNGQyQRUlbqAiW9wM7ctfv3USPEkzTgg==", "license": "BSD-3-Clause" }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, "node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -7480,9 +8578,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", "dev": true, "license": "MIT" }, @@ -7611,6 +8709,7 @@ "version": "5.44.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "dev": true, "license": "BSD-2-Clause", "optional": true, "peer": true, @@ -7680,6 +8779,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "optional": true, "peer": true, @@ -7694,6 +8794,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, "license": "MIT", "optional": true, "peer": true @@ -7767,9 +8868,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -7777,21 +8878,21 @@ } }, "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.86" + "tldts-core": "^7.0.27" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", "license": "MIT" }, "node_modules/to-arraybuffer": { @@ -7898,27 +8999,27 @@ } }, "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", "license": "BSD-3-Clause", "dependencies": { - "tldts": "^6.1.32" + "tldts": "^7.0.5" }, "engines": { "node": ">=16" } }, "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/transformation-matrix": { @@ -7927,6 +9028,14 @@ "integrity": "sha512-ThJH58GNFKhCw3gIoOtwf3tNwuYjbyEeiGdeq4mNMYWdJctnI896KUqn6PVt7jmNVepqa1bcKQtnMB1HtjsDMA==", "license": "MIT" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/tslog": { "version": "4.10.2", "resolved": "https://registry.npmjs.org/tslog/-/tslog-4.10.2.tgz", @@ -7984,6 +9093,21 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/unicode-trie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", @@ -8046,6 +9170,36 @@ "imurmurhash": "^0.1.4" } }, + "node_modules/unplugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", + "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -8196,16 +9350,16 @@ } }, "node_modules/vite": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", - "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "bin": { @@ -8222,9 +9376,10 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", - "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -8237,13 +9392,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { + "optional": true + }, + "jiti": { "optional": true }, - "lightningcss": { + "less": { "optional": true }, "sass": { @@ -8270,57 +9428,58 @@ } }, "node_modules/vite-plugin-node-polyfills": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.24.0.tgz", - "integrity": "sha512-GA9QKLH+vIM8NPaGA+o2t8PDfFUl32J8rUp1zQfMKVJQiNkOX4unE51tR6ppl6iKw5yOrDAdSH7r/UIFLCVhLw==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.26.0.tgz", + "integrity": "sha512-BAe5YzJf368XGev02hDvioidx4uVH8dqEJlG73bjQSxM26/AQnGcKFomq9n3vGq5yqpSHKN4h1XQNxx9l98mBg==", "dev": true, "license": "MIT", "dependencies": { "@rollup/plugin-inject": "^5.0.5", - "node-stdlib-browser": "^1.2.0" + "node-stdlib-browser": "^1.3.1" }, "funding": { "url": "https://github.com/sponsors/davidmyersdev" }, "peerDependencies": { - "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/vite-plugin-wasm": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.5.0.tgz", - "integrity": "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.6.0.tgz", + "integrity": "sha512-mL/QPziiIA4RAA6DkaZZzOstdwbW5jO4Vz7Zenj0wieKWBlNvIvX5L5ljum9lcUX0ShNfBgCNLKTjNkRVVqcsw==", + "dev": true, "license": "MIT", "peerDependencies": { - "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" + "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "node_modules/vitest": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", - "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", + "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.0.16", - "@vitest/mocker": "4.0.16", - "@vitest/pretty-format": "4.0.16", - "@vitest/runner": "4.0.16", - "@vitest/snapshot": "4.0.16", - "@vitest/spy": "4.0.16", - "@vitest/utils": "4.0.16", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", + "@vitest/expect": "4.1.2", + "@vitest/mocker": "4.1.2", + "@vitest/pretty-format": "4.1.2", + "@vitest/runner": "4.1.2", + "@vitest/snapshot": "4.1.2", + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", - "std-env": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -8336,12 +9495,13 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.16", - "@vitest/browser-preview": "4.0.16", - "@vitest/browser-webdriverio": "4.0.16", - "@vitest/ui": "4.0.16", + "@vitest/browser-playwright": "4.1.2", + "@vitest/browser-preview": "4.1.2", + "@vitest/browser-webdriverio": "4.1.2", + "@vitest/ui": "4.1.2", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { @@ -8370,6 +9530,9 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, @@ -8386,16 +9549,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz", - "integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==", + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz", + "integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.21", - "@vue/compiler-sfc": "3.5.21", - "@vue/runtime-dom": "3.5.21", - "@vue/server-renderer": "3.5.21", - "@vue/shared": "3.5.21" + "@vue/compiler-dom": "3.5.31", + "@vue/compiler-sfc": "3.5.31", + "@vue/runtime-dom": "3.5.31", + "@vue/server-renderer": "3.5.31", + "@vue/shared": "3.5.31" }, "peerDependencies": { "typescript": "*" @@ -8407,18 +9570,88 @@ } }, "node_modules/vue-router": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", - "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.4" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.4.tgz", + "integrity": "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.28.6", + "@vue-macros/common": "^3.1.1", + "@vue/devtools-api": "^8.0.6", + "ast-walker-scope": "^0.8.3", + "chokidar": "^5.0.0", + "json5": "^2.2.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "mlly": "^1.8.0", + "muggle-string": "^0.4.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "scule": "^1.3.0", + "tinyglobby": "^0.2.15", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1", + "yaml": "^2.8.2" }, "funding": { "url": "https://github.com/sponsors/posva" }, "peerDependencies": { - "vue": "^3.2.0" + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.17", + "pinia": "^3.0.4", + "vue": "^3.5.0" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + } + } + }, + "node_modules/vue-router/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/vue-router/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/vue-router/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/w3c-xmlserializer": { @@ -8602,19 +9835,13 @@ "node": ">=0.10" } }, - "node_modules/web-worker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz", - "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==", - "license": "Apache-2.0" - }, "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">=20" } }, "node_modules/webpack": { @@ -8678,6 +9905,12 @@ "source-map": "~0.6.1" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, "node_modules/webpack/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -8719,38 +9952,27 @@ "license": "MIT", "peer": true }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "license": "MIT", "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/which-typed-array": { @@ -8882,27 +10104,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -8941,6 +10142,21 @@ "license": "ISC", "peer": true }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 3b8c3bce..9156be43 100644 --- a/package.json +++ b/package.json @@ -17,17 +17,17 @@ "pinia-plugin-persistedstate": "^4.7.1", "scratch-parser": "5.1.1", "scratch-render": "github:hyperquark/scratch-render#develop", - "scratch-sb1-converter": "2.0.228", - "vue": "3.5.21", - "vue-router": "4.5.1", + "scratch-sb1-converter": "1.0.324", + "vue": "3.5.31", + "vue-router": "5.0.4", "wasm-feature-detect": "1.8.0" }, "devDependencies": { - "@vitejs/plugin-vue": "6.0.1", - "prettier": "^3.7.4", - "vite": "7.3.0", - "vite-plugin-node-polyfills": "0.24.0", - "vite-plugin-wasm": "3.5.0", - "vitest": "^4.0.16" + "@vitejs/plugin-vue": "6.0.5", + "prettier": "^3.8.1", + "vite": "8.0.3", + "vite-plugin-node-polyfills": "0.26.0", + "vite-plugin-wasm": "3.6.0", + "vitest": "^4.1.2" } } diff --git a/playground/components/ProjectFileInput.vue b/playground/components/ProjectFileInput.vue index 8338fb64..9e2c210a 100644 --- a/playground/components/ProjectFileInput.vue +++ b/playground/components/ProjectFileInput.vue @@ -19,11 +19,7 @@ const fileStore = useProjectFileStore(); async function handleFileInput() { loading.value = true; - console.log(fileInput); let file = fileInput.value.files[0]; - console.log(file); - //console.log(Buffer.from(file.arrayBuffer())); - //console.log(file.arrayBuffer()); if (!file) { emit("error", "file doesn't exist"); loading.value = false; @@ -31,11 +27,9 @@ async function handleFileInput() { } let json, zip, res; try { - //console.log(Buffer.from(file.arrayBuffer())); res = await unpackProject(await file.arrayBuffer()); [json, zip] = res; } catch (e) { - console.log("error", e); emit( "error", e.hasOwnProperty("validationError") @@ -45,7 +39,6 @@ async function handleFileInput() { loading.value = false; return; } - console.log(res, json, zip); fileStore.json = json; fileStore.title = file.name.replace(/\..+?$/, ""); fileStore.zip = zip; diff --git a/playground/components/ProjectIdPlayer.vue b/playground/components/ProjectIdPlayer.vue index 20c866c7..e5780de6 100644 --- a/playground/components/ProjectIdPlayer.vue +++ b/playground/components/ProjectIdPlayer.vue @@ -36,7 +36,7 @@ const description = ref(""); try { const apiRes = await ( await fetch(`https://trampoline.turbowarp.org/api/projects/${props.id}/`) - ).json(); //.project_token; + ).json(); title.value = apiRes.title; author.value = apiRes.author.username; instructions.value = apiRes.instructions; @@ -48,7 +48,6 @@ try { throw new Error("response was not OK"); } const [_json, _zip] = await unpackProject(await res.arrayBuffer()); - console.log(json); zip.value = _zip; json.value = _json; success.value = true; diff --git a/playground/components/ProjectPlayer.vue b/playground/components/ProjectPlayer.vue index 63a8f369..6e5c8e04 100644 --- a/playground/components/ProjectPlayer.vue +++ b/playground/components/ProjectPlayer.vue @@ -161,10 +161,8 @@ onMounted(async () => { const load_asset = async (md5ext) => { try { if (props.zip) { - console.log(props.zip); const file = props.zip.file(md5ext) ?? props.zip.files[md5ext]; - const data = await file.async("text"); //.then(console.log); - //console.log(file, data); + const data = await file.async("text"); return data; } return await ( @@ -181,8 +179,6 @@ onMounted(async () => { let assets = null; let wasmProject; - console.log(props); - try { // imports can take a long time, so need to wait for worker to tell us that it's initialised await new Promise((resolve) => { @@ -202,19 +198,7 @@ onMounted(async () => { proj: JSON.stringify(props.json), flags: getSettings().to_js(), }); - console.log("compile message posted!"); }); - - console.log( - wasmProject.target_names, - wasmProject.strings, - wasmProject.wasm_bytes, - ); - - // if ((!wasmProject instanceof FinishedWasm)) { - // throw new Error("unknown error occurred when compiling project"); - // } - wasmBytes = wasmProject.wasm_bytes; } catch (e) { declareError(e, true, "An error", "compiling"); @@ -237,7 +221,6 @@ onMounted(async () => { }, [wasmBytes.buffer], ); - console.log("optimise message posted!"); }); } catch (e) { declareError( @@ -362,7 +345,6 @@ onMounted(async () => { setAnswer = runner.setAnswer.bind(runner); mark_question_resolved = runner.mark_question_resolved.bind(runner); monitors.value = runner.monitors; - console.log(monitors.value); } catch (e) { declareError(e, true, "An error", "instantiating"); } diff --git a/playground/lib/compile-worker.js b/playground/lib/compile-worker.js index 3f146371..79d20b4c 100644 --- a/playground/lib/compile-worker.js +++ b/playground/lib/compile-worker.js @@ -7,7 +7,6 @@ postMessage("ready"); addEventListener("message", ({ data }) => { switch (data.stage) { case "compile": { - console.log("compile message received"); let wasmProject = sb3_to_wasm(data.proj, WasmFlags.from_js(data.flags)); postMessage( { @@ -20,8 +19,6 @@ addEventListener("message", ({ data }) => { break; } case "optimise": { - console.log("optimise message received!"); - console.log(data); import("binaryen").then((imports) => { const binaryen = imports.default; const binaryenModule = binaryen.readBinary(data.wasmBytes); diff --git a/playground/lib/project-loader.js b/playground/lib/project-loader.js index 5741dc8c..3769e5b1 100644 --- a/playground/lib/project-loader.js +++ b/playground/lib/project-loader.js @@ -10,7 +10,6 @@ export const unpackProject = (input) => { if (typeof input !== "string") { input = Buffer.from(input); } - console.log(input); return new Promise((resolve, reject) => { // The second argument of false below indicates to the validator that the // input should be parsed/validated as an entire project (and not a single sprite) @@ -40,16 +39,11 @@ export const unpackProject = (input) => { return Promise.reject(error); }) .then(async ([json, zip]) => { - console.log(Object.keys(json), json.projectVersion); if (json.projectVersion === 3) { return [json, zip]; } if (json.projectVersion === 2) { const { default: ScratchVM } = await import("@scratch/scratch-vm"); - // const scratchVm = - // typeof window === "object" - // ? await import("@scratch/scratch-vm/dist/web/scratch-vm.js") - // : await import("@scratch/scratch-vm/dist/node/scratch-vm.js"); const VM = new ScratchVM(); await VM.deserializeProject(json, zip); VM.runtime.handleProjectLoaded(); diff --git a/playground/lib/settings.js b/playground/lib/settings.js index 926e0776..3e1072bb 100644 --- a/playground/lib/settings.js +++ b/playground/lib/settings.js @@ -8,7 +8,6 @@ import { import * as WasmFeatureDetect from "wasm-feature-detect"; export { WasmFlags }; -console.log(WasmFeature); if (typeof window !== "undefined") window.hyperquarkExports = hyperquarkExports; export const supportedWasmFeatures = await getSupportedWasmFeatures(); @@ -74,7 +73,6 @@ export function getSettings() { * @param {WasmFlags} settings */ export function saveSettings(settings) { - console.log(settings.to_js()); localStorage["settings"] = JSON.stringify(settings.to_js()); } @@ -91,14 +89,11 @@ export async function getSupportedWasmFeatures() { return featureSet; } -console.log(await getSupportedWasmFeatures()); - /** * @returns {Set} */ export function getUsedWasmFeatures() { const settings = getSettings().to_js(); - console.log(settings); const featureSet = new Set(); for (const [id, info] of Object.entries(settingsInfo)) { if (info.type === "radio") { diff --git a/playground/lib/setup.js b/playground/lib/setup.js index fd326920..42645d4b 100644 --- a/playground/lib/setup.js +++ b/playground/lib/setup.js @@ -36,7 +36,6 @@ export async function setup( window.renderer = renderer; } renderer.setLayerGroupOrdering(["background", "video", "pen", "sprite"]); - //window.open(URL.createObjectURL(new Blob([wasm_bytes], { type: "octet/stream" }))); const pen_skin = renderer.createSkin("pen", "pen")[0]; const target_skins = project_json.targets.map((target, index) => { @@ -67,7 +66,6 @@ export async function setup( } return [skin, drawableId]; }); - console.log(target_skins); const stageIndex = project_json.targets.findIndex((target) => target.isStage); diff --git a/playground/views/Settings.vue b/playground/views/Settings.vue index 3bc1b1a8..d9cb3848 100644 --- a/playground/views/Settings.vue +++ b/playground/views/Settings.vue @@ -56,16 +56,9 @@ import { defaultSettings, } from "../lib/settings.js"; -console.log(settingsInfo); -console.log(defaultSettings.to_js()); - const settings = reactive(getSettings().to_js()); const route = useRoute(); -console.log(getUsedWasmFeatures()); const wasmFeatures = ref(getUsedWasmFeatures()); -console.log(wasmFeatures); - -console.log(settings); function scrollToAnchor() { if (!!route.hash) { @@ -89,7 +82,6 @@ window.settings = settings; window.WasmFlags = WasmFlags; watch(settings, (value, oldValue) => { - console.log(settings); saveSettings(WasmFlags.from_js(settings)); }); @@ -97,7 +89,6 @@ watch( settings, () => { wasmFeatures.value = getUsedWasmFeatures(); - console.log(wasmFeatures); }, { immediate: true, diff --git a/src/alloc.rs b/src/alloc.rs deleted file mode 100644 index 92d488ec..00000000 --- a/src/alloc.rs +++ /dev/null @@ -1,16 +0,0 @@ -use buddy_alloc::{BuddyAllocParam, FastAllocParam, NonThreadsafeAlloc}; - -// These values can be tuned -const FAST_HEAP_SIZE: usize = 4 * 1024; // 4 KB -const HEAP_SIZE: usize = 16 * 1024; // 16 KB -const LEAF_SIZE: usize = 16; - -static mut FAST_HEAP: [u8; FAST_HEAP_SIZE] = [0u8; FAST_HEAP_SIZE]; -static mut HEAP: [u8; HEAP_SIZE] = [0u8; HEAP_SIZE]; - -#[global_allocator] -static ALLOC: NonThreadsafeAlloc = unsafe { - let fast_param = FastAllocParam::new(FAST_HEAP.as_ptr(), FAST_HEAP_SIZE); - let buddy_param = BuddyAllocParam::new(HEAP.as_ptr(), HEAP_SIZE, LEAF_SIZE); - NonThreadsafeAlloc::new(fast_param, buddy_param) -}; diff --git a/src/error.rs b/src/error.rs index 18210a82..a7c88539 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use wasm_bindgen::JsValue; pub type HQResult = Result; -#[derive(Clone, Debug)] // todo: get rid of this once all expects are gone +#[derive(Clone, Debug)] pub struct HQError { pub err_type: HQErrorType, pub msg: Box, @@ -13,7 +13,7 @@ pub struct HQError { pub line: u32, pub column: u32, } -#[derive(Clone, Debug, PartialEq, Eq)] // todo: get rid of this once all expects are gone +#[derive(Clone, Debug, PartialEq, Eq)] pub enum HQErrorType { MalformedProject, InternalError, diff --git a/src/instructions.rs b/src/instructions.rs index 24732c69..5403b5b2 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -40,10 +40,8 @@ where use crate::ir::ReturnType; if inputs.is_empty() { let out = outputs_func(Rc::from([]))?; - // crate::log!("{out:?}"); Ok(out) } else { - // crate::log!("{inputs:?}"); if inputs.iter().any(crate::ir::Type::is_none) { hq_bug!("got none input type :scream:") } @@ -56,7 +54,6 @@ where tys.iter().map(move |ty| ty.and(input)) }) .collect::>(); - // crate::log!("{mapped:?}"); mapped .iter() .cloned() diff --git a/src/instructions/data/listcontents.rs b/src/instructions/data/listcontents.rs index 63c1e66e..d1298882 100644 --- a/src/instructions/data/listcontents.rs +++ b/src/instructions/data/listcontents.rs @@ -258,7 +258,6 @@ pub fn acceptable_inputs(_fields: &Fields) -> HQResult> { } pub fn output_type(_inputs: Rc<[IrType]>, _fields: &Fields) -> HQResult { - // output type includes string as we return empty string for out-of-bounds Ok(Singleton(IrType::String)) } diff --git a/src/instructions/data/replaceitemoflist.rs b/src/instructions/data/replaceitemoflist.rs index b5f8a6b5..f46b78fe 100644 --- a/src/instructions/data/replaceitemoflist.rs +++ b/src/instructions/data/replaceitemoflist.rs @@ -79,7 +79,6 @@ pub fn acceptable_inputs(Fields { list }: &Fields) -> HQResult> { } pub fn output_type(_inputs: Rc<[IrType]>, _fields: &Fields) -> HQResult { - // output type includes string as we return empty string for out-of-bounds Ok(ReturnType::None) } diff --git a/src/instructions/hq/cast.rs b/src/instructions/hq/cast.rs index 42075e95..0d1190bf 100644 --- a/src/instructions/hq/cast.rs +++ b/src/instructions/hq/cast.rs @@ -208,7 +208,6 @@ pub fn acceptable_inputs(_fields: &Fields) -> HQResult> { } pub fn output_type(inputs: Rc<[IrType]>, &Fields(to): &Fields) -> HQResult { - // crate::log!("{:?}", inputs[0]); Ok(Singleton( inputs[0] .base_types() diff --git a/src/instructions/operator/modulo.rs b/src/instructions/operator/modulo.rs index 817d54d8..8c9f6f6e 100644 --- a/src/instructions/operator/modulo.rs +++ b/src/instructions/operator/modulo.rs @@ -8,7 +8,6 @@ pub fn wasm(func: &StepFunc, inputs: Rc<[IrType]>) -> HQResult, - ignore_variables: bool, - recurse: bool, -) -> HQResult<()> { - let mut type_stack: Vec<(IrType, usize)> = vec![]; // a vector of types, and where they came from - let mut casts: Vec<(usize, IrType)> = vec![]; // a vector of cast targets, and where they're needed - for (i, block) in blocks.iter().enumerate() { - let mut expected_inputs = if ignore_variables - && (matches!( - block, - IrOpcode::data_setvariableto(DataSetvariabletoFields { - local_write, .. - }) if !*local_write.borrow()) - || matches!(block, - IrOpcode::data_teevariable(DataTeevariableFields { local_read_write, .. }) if !*local_read_write.borrow()) - || matches!( - block, - IrOpcode::data_addtolist(_) | IrOpcode::data_replaceitemoflist(_) - )) { - vec![IrType::Any] - } else { - block - .acceptable_inputs()? - .iter() - .copied() - .map(|ty| if ty.is_none() { IrType::Any } else { ty }) - .collect::>() - }; - if type_stack.len() < expected_inputs.len() { - hq_bug!( - "didn't have enough inputs on the type stack\nat block {}", - block - ); - } - let actual_inputs: Vec<_> = type_stack - .splice((type_stack.len() - expected_inputs.len()).., []) - .collect(); - // crate::log!("{}; {:?}; {:?}", block, expected_inputs, actual_inputs); - let mut dummy_actual_inputs: Vec<_> = actual_inputs.iter().map(|a| a.0).collect(); - for (j, (expected, actual)) in - core::iter::zip(expected_inputs.clone().into_iter(), actual_inputs).enumerate() - { - if !expected.is_none() - && !expected - .base_types() - .fold(IrType::none(), IrType::or) - .contains(actual.0) - { - if matches!( - block, - IrOpcode::data_setvariableto(_) - | IrOpcode::data_teevariable(_) - | IrOpcode::data_addtolist(_) - | IrOpcode::data_replaceitemoflist(_) - ) { - hq_bug!( - "attempted to insert a cast before a variable/list operation - variables \ - should encompass all possible types, rather than causing values to be \ - coerced. - Tried to cast from {} (at position {}) to {} (at position {}). - Occurred on these opcodes: [ - {} - ]", - actual.0, - actual.1, - expected, - i, - blocks.iter().map(|block| format!("{block}")).join(",\n"), - ) - } - casts.push((actual.1, expected)); - dummy_actual_inputs[j] = expected; - expected_inputs[j] = IrOpcode::hq_cast(HqCastFields(expected)) - .output_type(Rc::from([if actual.0.is_none() { - IrType::Any - } else { - actual.0 - }]))? - .singleton_or_else(|| { - make_hq_bug!("hq_cast returned no output type, or multiple output types") - })?; - } - } - if ignore_variables - && (matches!( - block, - IrOpcode::data_variable(DataVariableFields { - local_read, .. - }) if !*local_read.borrow()) - || matches!(block, - IrOpcode::data_teevariable(DataTeevariableFields { local_read_write, .. }) if !*local_read_write.borrow()) - || matches!( - block, - IrOpcode::data_itemoflist(_) | IrOpcode::procedures_argument(_) - )) - { - type_stack.push((IrType::Any, i)); - } else { - match block.output_type(Rc::from(dummy_actual_inputs))? { - ReturnType::Singleton(output) => type_stack.push((output, i)), - ReturnType::MultiValue(outputs) => { - type_stack.extend(outputs.iter().copied().zip(core::iter::repeat(i))); - } - ReturnType::None => (), - } - } - - if recurse && let Some(inline_steps) = block.inline_steps(false) { - for inline_step in inline_steps { - insert_casts( - inline_step.try_borrow_mut()?.opcodes_mut(), - ignore_variables, - true, - )?; - } - } - } - for (pos, ty) in casts.into_iter().rev() { - blocks.insert(pos + 1, IrOpcode::hq_cast(HqCastFields(ty))); - } - Ok(()) -} - -#[derive(Clone, Debug)] -pub enum NextBlock { - ID(Box), - Step(Step), - StepIndex(StepIndex), -} - -#[derive(Clone, Debug)] -pub struct NextBlockInfo { - pub yield_first: bool, - pub block: NextBlock, -} - -/// contains a vector of next blocks, as well as information on how to proceed when -/// there are no next blocks: true => terminate the thread, false => do nothing -/// (useful for e.g. loop bodies, or for non-stack blocks) -#[derive(Clone, Debug)] -pub struct NextBlocks(Vec, bool); - -impl NextBlocks { - pub const fn new(terminating: bool) -> Self { - Self(vec![], terminating) - } - - pub const fn terminating(&self) -> bool { - self.1 - } - - pub fn extend_with_inner(&self, new: NextBlockInfo) -> Self { - let mut cloned = self.0.clone(); - cloned.push(new); - Self(cloned, self.terminating()) - } - - pub fn pop_inner(self) -> (Option, Self) { - let terminating = self.terminating(); - let mut vec = self.0; - let popped = vec.pop(); - (popped, Self(vec, terminating)) - } -} pub fn from_block( block: &Block, @@ -211,712 +56,86 @@ pub fn from_block( Ok(opcodes) } -pub fn input_names(block_info: &BlockInfo, context: &StepContext) -> HQResult> { - let opcode = &block_info.opcode; - // target and procs need to be declared outside of the match block - // to prevent lifetime issues - let target = context.target(); - let procs = target.procedures()?; - Ok( - #[expect( - clippy::wildcard_enum_match_arm, - reason = "too many opcodes to match individually" - )] - match opcode { - BlockOpcode::looks_say | BlockOpcode::looks_think => vec!["MESSAGE"], - BlockOpcode::operator_add - | BlockOpcode::operator_divide - | BlockOpcode::operator_subtract - | BlockOpcode::operator_multiply - | BlockOpcode::operator_mod => vec!["NUM1", "NUM2"], - BlockOpcode::operator_mathop | BlockOpcode::operator_round => vec!["NUM"], - BlockOpcode::operator_lt - | BlockOpcode::operator_gt - | BlockOpcode::operator_equals - | BlockOpcode::operator_and - | BlockOpcode::operator_or => vec!["OPERAND1", "OPERAND2"], - BlockOpcode::operator_join | BlockOpcode::operator_contains => { - vec!["STRING1", "STRING2"] - } - BlockOpcode::operator_letter_of => vec!["LETTER", "STRING"], - BlockOpcode::motion_gotoxy => vec!["X", "Y"], - BlockOpcode::motion_movesteps => vec!["STEPS"], - BlockOpcode::motion_pointindirection => vec!["DIRECTION"], - BlockOpcode::motion_turnleft | BlockOpcode::motion_turnright => vec!["DEGREES"], - BlockOpcode::sensing_keypressed => vec!["KEY_OPTION"], - BlockOpcode::sensing_dayssince2000 - | BlockOpcode::data_variable - | BlockOpcode::argument_reporter_boolean - | BlockOpcode::argument_reporter_string_number - | BlockOpcode::looks_costume - | BlockOpcode::looks_size - | BlockOpcode::looks_nextcostume - | BlockOpcode::looks_costumenumbername - | BlockOpcode::looks_backdrops - | BlockOpcode::looks_hide - | BlockOpcode::looks_show - | BlockOpcode::pen_penDown - | BlockOpcode::pen_penUp - | BlockOpcode::pen_clear - | BlockOpcode::control_forever - | BlockOpcode::pen_menu_colorParam - | BlockOpcode::motion_direction - | BlockOpcode::data_deletealloflist - | BlockOpcode::data_lengthoflist - | BlockOpcode::data_listcontents - | BlockOpcode::control_stop - | BlockOpcode::event_broadcast_menu - | BlockOpcode::sensing_timer - | BlockOpcode::sensing_resettimer - | BlockOpcode::sensing_answer - | BlockOpcode::looks_backdropnumbername - | BlockOpcode::looks_nextbackdrop - | BlockOpcode::data_showvariable - | BlockOpcode::data_hidevariable - | BlockOpcode::sensing_mousex - | BlockOpcode::sensing_mousey - | BlockOpcode::sensing_mousedown - | BlockOpcode::motion_xposition - | BlockOpcode::motion_yposition - | BlockOpcode::sensing_keyoptions => vec![], - BlockOpcode::sensing_askandwait => vec!["QUESTION"], - BlockOpcode::event_broadcast | BlockOpcode::event_broadcastandwait => { - vec!["BROADCAST_INPUT"] - } - BlockOpcode::control_wait => vec!["DURATION"], - BlockOpcode::data_setvariableto - | BlockOpcode::data_changevariableby - | BlockOpcode::control_for_each => vec!["VALUE"], - BlockOpcode::operator_random => vec!["FROM", "TO"], - BlockOpcode::pen_setPenColorParamTo | BlockOpcode::pen_changePenColorParamBy => { - vec!["COLOR_PARAM", "VALUE"] - } - BlockOpcode::control_if - | BlockOpcode::control_if_else - | BlockOpcode::control_repeat_until - | BlockOpcode::control_while - | BlockOpcode::control_wait_until => vec!["CONDITION"], - BlockOpcode::operator_not => vec!["OPERAND"], - BlockOpcode::control_repeat => vec!["TIMES"], - BlockOpcode::operator_length => vec!["STRING"], - BlockOpcode::looks_switchcostumeto => vec!["COSTUME"], - BlockOpcode::looks_switchbackdropto => vec!["BACKDROP"], - BlockOpcode::looks_setsizeto | BlockOpcode::pen_setPenSizeTo => vec!["SIZE"], - BlockOpcode::looks_changesizeby => vec!["CHANGE"], - BlockOpcode::pen_setPenColorToColor => vec!["COLOR"], - BlockOpcode::data_addtolist - | BlockOpcode::data_itemnumoflist - | BlockOpcode::data_listcontainsitem => vec!["ITEM"], - BlockOpcode::data_itemoflist | BlockOpcode::data_deleteoflist => vec!["INDEX"], - BlockOpcode::data_replaceitemoflist | BlockOpcode::data_insertatlist => { - vec!["INDEX", "ITEM"] - } - BlockOpcode::motion_changexby => vec!["DX"], - BlockOpcode::motion_changeyby => vec!["DY"], - BlockOpcode::motion_setx => vec!["X"], - BlockOpcode::motion_sety => vec!["Y"], - BlockOpcode::procedures_call => 'proc_block: { - let serde_json::Value::String(proccode) = block_info - .mutation - .mutations - .get("proccode") - .ok_or_else(|| make_hq_bad_proj!("missing proccode on procedures_call"))? - else { - hq_bad_proj!("non-string proccode on procedures_call"); - }; - let Some(proc) = procs.get(proccode.as_str()) else { - break 'proc_block vec![]; - }; - proc.arg_ids().iter().map(|b| &**b).collect() - } - other => hq_todo!("unimplemented input_names for {:?}", other), - } - .into_iter() - .map(String::from) - .collect(), - ) -} - -pub fn inputs( +fn from_normal_block( block_info: &BlockInfo, blocks: &BlockMap, context: &StepContext, project: &Weak, - flags: &WasmFlags, -) -> HQResult> { - Ok(input_names(block_info, context)? - .into_iter() - .map(|name| -> HQResult> { - let input = match block_info.inputs.get((*name).into()) { - Some(noshadow @ Input::NoShadow(_, Some(_))) => noshadow, - Some(shadow @ Input::Shadow(_, Some(_), _)) => shadow, - None | Some(Input::NoShadow(_, None) | Input::Shadow(_, None, _)) => { - // revert to a sensible default - &Input::NoShadow( - 0, - Some(BlockArrayOrId::Array(BlockArray::NumberOrAngle(6, 0.0))), - ) - // if name.starts_with("CONDITION") { - // &Input::NoShadow( - // 0, - // Some(BlockArrayOrId::Array(BlockArray::NumberOrAngle(6, 0.0))), - // ) - // } else { - // hq_bad_proj!("missing input {}", name) - // } - } - }; - #[expect( - clippy::wildcard_enum_match_arm, - reason = "all variants covered in previous match guards" - )] - match input { - Input::NoShadow(_, Some(block)) | Input::Shadow(_, Some(block), _) => match block { - BlockArrayOrId::Array(arr) => { - Ok(vec![from_special_block(arr, context, flags)?]) - } - BlockArrayOrId::Id(id) => from_block( - blocks.get(id).ok_or_else(|| { - make_hq_bad_proj!("block for input {} doesn't exist", name) - })?, - blocks, - context, - project, - NextBlocks::new(false), - flags, - ), - }, - _ => hq_bad_proj!("missing input block for {}", name), - } - }) - .collect::>>()? - .iter() - .flatten() - .cloned() - .collect()) -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ProcArgType { - Boolean, - StringNumber, -} - -fn procedure_argument( - _arg_type: ProcArgType, - block_info: &BlockInfo, - context: &StepContext, -) -> HQResult> { - let Some(proc_context) = context.proc_context.clone() else { - // this is always the default, regardless of type - return Ok(vec![IrOpcode::hq_integer(HqIntegerFields(0))]); - }; - let sb3::VarVal::String(arg_name) = block_info - .fields - .get("VALUE") - .ok_or_else(|| make_hq_bad_proj!("missing VALUE field for proc argument"))? - .get_0() - .ok_or_else(|| make_hq_bad_proj!("missing value of VALUE field"))? - else { - hq_bad_proj!("non-string proc argument name") - }; - let Some(index) = proc_context - .arg_names - .iter() - .position(|name| name == arg_name) - else { - return Ok(vec![IrOpcode::hq_integer(HqIntegerFields(0))]); - }; - let arg_vars = (*proc_context.arg_vars).borrow(); - let arg_var = arg_vars - .get(index) - .ok_or_else(|| make_hq_bad_proj!("argument index not in range of argumenttypes"))?; - Ok(vec![IrOpcode::procedures_argument( - ProceduresArgumentFields { - index, - arg_var: arg_var.clone(), - in_warped: context.warp, - arg_vars: Rc::clone(&proc_context.arg_vars), - }, - )]) -} - -/// Generates the next step to go to, based off of the block info, and the `NextBlocks` -/// passed to it. -/// -/// Returns a `MaybeInlinedStep` along with a `bool` indicating if the step should yield -/// first. -fn generate_next_step( - block_info: &BlockInfo, - blocks: &BTreeMap, Block>, - context: &StepContext, - final_next_blocks: NextBlocks, - flags: &WasmFlags, -) -> HQResult<(MaybeInlinedStep, bool)> { - let (next_block, yield_first, outer_next_blocks) = if let Some(ref next_block) = block_info.next - { - ( - Some(NextBlock::ID(next_block.clone())), - false, - final_next_blocks, - ) - } else if let (Some(next_block_info), popped_next_blocks) = - final_next_blocks.clone().pop_inner() - { - ( - Some(next_block_info.block), - next_block_info.yield_first, - popped_next_blocks, - ) - } else { - (None, false, final_next_blocks) - }; - let next_step = match next_block { - Some(NextBlock::ID(id)) => { - let Some(next_block_block) = blocks.get(&id) else { - hq_bad_proj!("next block doesn't exist") - }; - MaybeInlinedStep::Undetermined(Step::from_block( - next_block_block, - id, - blocks, - context, - &context.target().project(), - outer_next_blocks, - false, - flags, - )?) - } - Some(NextBlock::Step(step)) => MaybeInlinedStep::Undetermined(step), - Some(NextBlock::StepIndex(step_index)) => MaybeInlinedStep::NonInlined(step_index), - None => MaybeInlinedStep::Undetermined(Step::new_terminating( - context.clone(), - context.target().project(), - false, - )), - }; - Ok((next_step, yield_first)) -} - -fn generate_next_step_inlined( - block_info: &BlockInfo, - blocks: &BTreeMap, Block>, - context: &StepContext, - final_next_blocks: NextBlocks, - flags: &WasmFlags, -) -> HQResult { - let (next_step, yield_first) = - generate_next_step(block_info, blocks, context, final_next_blocks, flags)?; - Ok(match next_step { - MaybeInlinedStep::Inlined(step) => { - if yield_first { - let next_step_index = step - .try_borrow()? - .clone_to_non_inlined(&context.target().project())?; - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Schedule(next_step_index), - })], - context.target().project(), - false, - ))) - } else { - step - } - } - MaybeInlinedStep::NonInlined(step_index) => { - if yield_first { - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Schedule(step_index), - })], - context.target().project(), - false, - ))) - } else { - let mut step = context - .project()? - .steps() - .try_borrow()? - .get(step_index.0) - .ok_or_else(|| make_hq_bug!("step index out of bounds"))? - .try_borrow()? - .clone(); - step.make_inlined(); - Rc::new(RefCell::new(step)) - } - } - MaybeInlinedStep::Undetermined(mut step) => { - if yield_first { - let step_index = step.clone_to_non_inlined(&context.target().project())?; - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Schedule(step_index), - })], - context.target().project(), - false, - ))) - } else { - step.make_inlined(); - Rc::new(RefCell::new(step)) - } - } - }) -} - -/// Generates a `StepIndex` for the next step of the given block, based on the -/// block info and the given `NextSteps`. The generated step must not be inlined -/// at a later stage, as it may cause reference cycles. -fn generate_next_step_non_inlined( - block_info: &BlockInfo, - blocks: &BTreeMap, Block>, - context: &StepContext, final_next_blocks: NextBlocks, flags: &WasmFlags, -) -> HQResult { - let (next_step, _yield_first) = - generate_next_step(block_info, blocks, context, final_next_blocks, flags)?; - match next_step { - MaybeInlinedStep::Inlined(step) => step - .try_borrow()? - .clone_to_non_inlined(&context.target().project()), - MaybeInlinedStep::NonInlined(step_index) => Ok(step_index), - MaybeInlinedStep::Undetermined(step) => { - step.clone_to_non_inlined(&context.target().project()) - } - } -} - -#[expect(clippy::too_many_arguments, reason = "too many arguments!")] -// TODO: put these arguments into a struct? -fn generate_loop( - warp: bool, - should_break: &mut bool, - block_info: &BlockInfo, - blocks: &BTreeMap, Block>, - context: &StepContext, - final_next_blocks: NextBlocks, - first_condition_instructions: Option>, - condition_instructions: Vec, - pre_body_instructions: Option>, - flip_if: bool, - setup_instructions: Vec, - flags: &WasmFlags, -) -> HQResult> { - let substack_id = match block_info.inputs.get("SUBSTACK") { - Some( - Input::NoShadow(_, Some(substack_input)) | Input::Shadow(_, Some(substack_input), _), - ) => { - let BlockArrayOrId::Id(id) = substack_input else { - hq_bad_proj!("malformed SUBSTACK input") - }; - Some(id) - } - _ => None, - }; - - let substack_block = if let Some(id) = substack_id { - Some( - blocks - .get(id) - .ok_or_else(|| make_hq_bad_proj!("SUBSTACK block doesn't seem to exist"))?, - ) - } else { - None - }; - if warp { - // TODO: can this be expressed in the same way as non-warping loops, - // just with yield_first: false? - let substack_blocks = if let Some(block) = substack_block { - from_block( - block, - blocks, - context, - &context.target().project(), - NextBlocks::new(false), - flags, - )? - } else { - vec![] - }; - let substack_step = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - substack_blocks, - context.target().project(), - false, - ))); - let condition_step = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - condition_instructions, - context.target().project(), - false, - ))); - let first_condition_step = first_condition_instructions.map(|instrs| { - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - instrs, - context.target().project(), - false, - ))) - }); - let pre_body_step = pre_body_instructions.map(|instrs| { - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - instrs, - context.target().project(), - false, - ))) - }); - Ok(setup_instructions - .into_iter() - .chain(vec![IrOpcode::control_loop(ControlLoopFields { - first_condition: first_condition_step, - condition: condition_step, - body: substack_step, - pre_body: pre_body_step, - flip_if, - })]) - .collect()) - } else { - *should_break = true; - let next_step = - generate_next_step_inlined(block_info, blocks, context, final_next_blocks, flags)?; - let project = context.project()?; - let mut condition_step = Step::new( - None, - context.clone(), - condition_instructions.clone(), - context.target().project(), - true, - ); - let substack_step = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![], - context.target().project(), - false, - ))); - condition_step - .opcodes_mut() - .push(IrOpcode::control_if_else(ControlIfElseFields { - branch_if: Rc::clone(if flip_if { &next_step } else { &substack_step }), - branch_else: Rc::clone(if flip_if { &substack_step } else { &next_step }), - })); - let condition_step_index = project.new_owned_step(condition_step)?; - if let Some(pre_body_blocks) = pre_body_instructions { - substack_step - .try_borrow_mut()? - .opcodes_mut() - .extend(pre_body_blocks); - } - if let Some(block) = substack_block { - let substack_blocks = from_block( - block, - blocks, - context, - &context.target().project(), - NextBlocks::new(false).extend_with_inner(NextBlockInfo { - yield_first: true, - block: NextBlock::StepIndex(condition_step_index), - }), - flags, - )?; - substack_step - .try_borrow_mut()? - .opcodes_mut() - .extend(substack_blocks); - } else { - substack_step - .try_borrow_mut()? - .opcodes_mut() - .push(IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Schedule(condition_step_index), - })); - } - Ok(setup_instructions - .into_iter() - .chain(first_condition_instructions.map_or(condition_instructions, |instrs| instrs)) - .chain(vec![IrOpcode::control_if_else(ControlIfElseFields { - branch_if: Rc::clone(if flip_if { &next_step } else { &substack_step }), - branch_else: Rc::clone(if flip_if { &substack_step } else { &next_step }), - // branch_if: Rc::clone(if flip_if { &next_step } else { &substack_step }), - // branch_else: Rc::clone(if flip_if { &substack_step } else { &next_step }), - })]) - .collect()) - } -} - -#[expect(clippy::too_many_arguments, reason = "will fix later. maybe.")] -fn generate_if_else( - if_block: (&Block, Box), - maybe_else_block: Option<(&Block, Box)>, - block_info: &BlockInfo, - final_next_blocks: &NextBlocks, - blocks: &BTreeMap, Block>, - context: &StepContext, - should_break: &mut bool, - flags: &WasmFlags, -) -> HQResult> { - // crate::log( - // format!( - // "generate_if_else (if block: {}) (else block?: {})", - // if_block.1, - // maybe_else_block.is_some() - // ) - // .as_str(), - // ); - - // let if_step_yields = dummy_if_step.does_yield()?; - // let else_step_yields = dummy_else_step.does_yield()?; - // crate::log(format!("if yields: {if_step_yields}, else yields: {else_step_yields}").as_str()); - if !context.warp { - let this_project = context.project()?; - let dummy_project = Rc::new(IrProject::new( - this_project.global_variables().clone(), - this_project.global_lists().clone(), - Box::from(this_project.broadcasts()), - 0, - vec![], - )); - let dummy_target = Rc::new(Target::new( - false, - context.target().variables().clone(), - context.target().lists().clone(), - Rc::downgrade(&dummy_project), - RefCell::new(context.target().procedures()?.clone()), - 0, - context.target().costumes().into(), - )); - dummy_project - .targets() - .try_borrow_mut() - .map_err(|_| make_hq_bug!("couldn't mutably borrow cell"))? - .insert("".into(), Rc::clone(&dummy_target)); - let dummy_context = StepContext { - target: Rc::clone(&dummy_target), - ..context.clone() - }; - let dummy_if_step = Step::from_block( - if_block.0, - if_block.1.clone(), - blocks, - &dummy_context, - &Rc::downgrade(&dummy_project), - NextBlocks::new(false), - false, - flags, - )?; - let dummy_else_step = if let Some((else_block, else_block_id)) = maybe_else_block.clone() { - Step::from_block( - else_block, - else_block_id, - blocks, - &dummy_context, - &Rc::downgrade(&dummy_project), - NextBlocks::new(false), - false, - flags, - )? - } else { - Step::new_empty( - Rc::downgrade(&dummy_project), - false, - Rc::clone(&dummy_target), - ) - }; - if dummy_if_step.does_yield() || dummy_else_step.does_yield() { - // TODO: ideally if only one branch yields then we'd duplicate the next step and put one - // version inline after the branch, and the other tagged on in the substep's NextBlocks - // as usual, to allow for extra variable type optimisations. - #[expect( - clippy::option_if_let_else, - reason = "map_or_else alternative is too complex" - )] - let (next_block, next_blocks) = if let Some(ref next_block) = block_info.next { - // crate::log("got next block from block_info.next"); - ( - Some(NextBlock::ID(next_block.clone())), - final_next_blocks.extend_with_inner(NextBlockInfo { - yield_first: false, - block: NextBlock::ID(next_block.clone()), - }), - ) - } else if let (Some(next_block_info), _) = final_next_blocks.clone().pop_inner() { - // crate::log("got next block from popping from final_next_blocks"); - (Some(next_block_info.block), final_next_blocks.clone()) - } else { - // crate::log("no next block found"); - ( - None, - final_next_blocks.clone(), // preserve termination behaviour - ) - }; - let final_if_step = { - Step::from_block( - if_block.0, - if_block.1, - blocks, - context, - &context.target().project(), - next_blocks.clone(), - false, - flags, - )? - // crate::log("recompiled if_step with correct next blocks"); - }; - let final_else_step = if let Some((else_block, else_block_id)) = maybe_else_block { - Step::from_block( - else_block, - else_block_id, +) -> HQResult> { + let mut curr_block = Some(block_info); + let mut final_next_blocks = final_next_blocks; + let mut opcodes = vec![]; + let mut should_break = false; + while let Some(block_info) = curr_block { + opcodes.append( + &mut inputs(block_info, blocks, context, project, flags)? + .into_iter() + .chain(block_to_ir( + block_info, blocks, context, - &context.target().project(), - next_blocks, - false, + project, + &final_next_blocks, flags, - )? - // crate::log("recompiled else step with correct next blocks"); - } else { - let opcode = match next_block { - Some(NextBlock::ID(id)) => { - let next_block = blocks - .get(&id) - .ok_or_else(|| make_hq_bad_proj!("missing next block"))?; - // crate::log( - // format!("got NextBlock::Id({id:?}), creating step from_block").as_str(), - // ); - vec![IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Inline(Rc::new(RefCell::new(Step::from_block( + &mut should_break, + )?) + .collect(), + ); + if should_break { + break; + } + curr_block = if let Some(ref next_id) = block_info.next { + let next_block = blocks + .get(next_id) + .ok_or_else(|| make_hq_bad_proj!("missing next block"))?; + next_block.block_info() + } else if let (Some(popped_next), new_next_blocks_stack) = + final_next_blocks.clone().pop_inner() + { + match popped_next.block { + NextBlock::ID(id) => { + let next_block = blocks + .get(&id) + .ok_or_else(|| make_hq_bad_proj!("missing next block"))?; + if (popped_next.yield_first) && !context.warp { + opcodes.push(IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Schedule(Step::from_block_non_inlined( next_block, id.clone(), blocks, context, - &context.target().project(), - next_blocks, - false, + project, + new_next_blocks_stack, flags, - )?))), - })] + )?), + })); + None + } else { + final_next_blocks = new_next_blocks_stack; + next_block.block_info() } - Some(NextBlock::Step(mut step)) => { + } + NextBlock::Step(mut step) => { + if popped_next.yield_first && !context.warp { + opcodes.push(IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Schedule(context.project()?.new_owned_step(step)?), + })); + } else { step.make_inlined(); - // crate::log(format!("got NextBlock::Step({:?})", rcstep.id()).as_str()); - - vec![IrOpcode::hq_yield(HqYieldFields { + opcodes.push(IrOpcode::hq_yield(HqYieldFields { mode: YieldMode::Inline(Rc::new(RefCell::new(step))), - })] + })); } - Some(NextBlock::StepIndex(step_index)) => { + None + } + NextBlock::StepIndex(step_index) => { + if popped_next.yield_first && !context.warp { + opcodes.push(IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Schedule(step_index), + })); + } else { let mut step = context .project()? .steps() @@ -926,2228 +145,9 @@ fn generate_if_else( .try_borrow()? .clone(); step.make_inlined(); - vec![IrOpcode::hq_yield(HqYieldFields { + opcodes.push(IrOpcode::hq_yield(HqYieldFields { mode: YieldMode::Inline(Rc::new(RefCell::new(step))), - })] - } - None => { - // crate::log("no next block after if!"); - if next_blocks.terminating() { - // crate::log("terminating after if/else\n"); - vec![IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::None, - })] - } else { - // crate::log("not terminating, at end of if/else"); - vec![] - } - } - }; - Step::new( - None, - context.clone(), - opcode, - context.target().project(), - false, - ) - }; - *should_break = true; - return Ok(vec![IrOpcode::control_if_else(ControlIfElseFields { - branch_if: Rc::new(RefCell::new(final_if_step)), - branch_else: Rc::new(RefCell::new(final_else_step)), - })]); - } - } - let final_if_step = Step::from_block( - if_block.0, - if_block.1, - blocks, - context, - &context.target().project(), - NextBlocks::new(false), - false, - flags, - )?; - let final_else_step = if let Some((else_block, else_block_id)) = maybe_else_block { - Step::from_block( - else_block, - else_block_id, - blocks, - context, - &context.target().project(), - NextBlocks::new(false), - false, - flags, - )? - } else { - Step::new( - None, - context.clone(), - vec![], - context.target().project(), - false, - ) - }; - Ok(vec![IrOpcode::control_if_else(ControlIfElseFields { - branch_if: Rc::new(RefCell::new(final_if_step)), - branch_else: Rc::new(RefCell::new(final_else_step)), - })]) -} - -fn generate_list_index_op( - list: &RcList, - block: B, - maybe_all_block: Option, - other_argument: bool, - add_one_to_length: bool, - default_output: Option<&IrOpcode>, - context: &StepContext, - project: &Weak, - flags: &WasmFlags, -) -> HQResult> -where - B: Fn() -> IrOpcode, -{ - let text_var = RcVar::new(IrType::String, &VarVal::String("".into()), None, flags)?; - let int_var = RcVar::new(IrType::Int, &VarVal::Int(0), None, flags)?; - let extra_var = RcVar::new_empty(); - let result_var = RcVar::new_empty(); - - let has_output = default_output.is_some(); - - let result_step = |mut opcodes: Vec| { - if has_output { - opcodes.push(IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(result_var.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(false), - })); - } - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - opcodes, - Weak::clone(project), - false, - ))) - }; - - let int_step = result_step(if other_argument { - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(int_var.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_cast(HqCastFields(IrType::Int)), - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(extra_var.clone()), - local_read: RefCell::new(true), - }), - block(), - ] - } else { - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(int_var.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_cast(HqCastFields(IrType::Int)), - block(), - ] - }); - - let last_step = result_step(if other_argument { - vec![IrOpcode::data_lengthoflist(DataLengthoflistFields { - list: list.clone(), - })] - .into_iter() - .chain(if add_one_to_length { - vec![ - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - ] - } else { - vec![] - }) - .chain(vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(extra_var.clone()), - local_read: RefCell::new(true), - }), - block(), - ]) - .collect() - } else { - vec![IrOpcode::data_lengthoflist(DataLengthoflistFields { - list: list.clone(), - })] - .into_iter() - .chain(if add_one_to_length { - vec![ - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - ] - } else { - vec![] - }) - .chain(vec![block()]) - .collect() - }); - - let random_step = result_step(if other_argument { - vec![ - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::data_lengthoflist(DataLengthoflistFields { list: list.clone() }), - ] - .into_iter() - .chain(if add_one_to_length { - vec![ - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - ] - } else { - vec![] - }) - .chain(vec![ - IrOpcode::operator_random, - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(extra_var.clone()), - local_read: RefCell::new(true), - }), - block(), - ]) - .collect() - } else { - vec![ - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::data_lengthoflist(DataLengthoflistFields { list: list.clone() }), - ] - .into_iter() - .chain(if add_one_to_length { - vec![ - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - ] - } else { - vec![] - }) - .chain(vec![IrOpcode::operator_random, block()]) - .collect() - }); - - let default_step = if let Some(default_block) = default_output { - result_step(vec![default_block.clone()]) - } else { - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![], - Weak::clone(project), - false, - ))) - }; - - let not_any_step = maybe_all_block.map(|all_block| { - let all_step = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![all_block], - Weak::clone(project), - false, - ))); - - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(text_var.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_text(HqTextFields("all".into())), - IrOpcode::operator_equals, - IrOpcode::control_if_else(ControlIfElseFields { - branch_if: all_step, - branch_else: Rc::clone(&default_step), - }), - ], - Weak::clone(project), - false, - ))) - }); - - let not_random_step = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(text_var.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_text(HqTextFields("any".into())), - IrOpcode::operator_equals, - IrOpcode::control_if_else(ControlIfElseFields { - branch_if: Rc::clone(&random_step), - branch_else: not_any_step.unwrap_or(default_step), - }), - ], - Weak::clone(project), - false, - ))); - - let not_last_step = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(text_var.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_text(HqTextFields("random".into())), - IrOpcode::operator_equals, - IrOpcode::control_if_else(ControlIfElseFields { - branch_if: random_step, - branch_else: not_random_step, - }), - ], - Weak::clone(project), - false, - ))); - - let not_int_step = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(text_var.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_text(HqTextFields("last".into())), - IrOpcode::operator_equals, - IrOpcode::control_if_else(ControlIfElseFields { - branch_if: last_step, - branch_else: not_last_step, - }), - ], - Weak::clone(project), - false, - ))); - - // we do some silly shennanigans with swapping to make sure that the SSA optimiser stays happy - Ok(if other_argument { - vec![IrOpcode::hq_swap] - } else { - vec![] - } - .into_iter() - .chain(vec![ - IrOpcode::hq_dup, - IrOpcode::hq_cast(HqCastFields(IrType::String)), - IrOpcode::hq_swap, - IrOpcode::hq_cast(HqCastFields(IrType::Int)), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(int_var.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(text_var), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - ]) - .chain(if other_argument { - vec![IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(extra_var), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - })] - } else { - vec![] - }) - .chain(vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(int_var), - local_read: RefCell::new(true), - }), - IrOpcode::hq_integer(HqIntegerFields(0)), - IrOpcode::operator_gt, - IrOpcode::control_if_else(ControlIfElseFields { - branch_if: int_step, - branch_else: not_int_step, - }), - ]) - .chain(if has_output { - vec![IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(result_var), - local_read: RefCell::new(true), - })] - } else { - vec![] - }) - .collect()) -} - -fn generate_exhaustive_string_comparison( - string_source: I, - instruction: F, - fallback: Vec, - context: &StepContext, - project: &Weak, - flags: &WasmFlags, -) -> HQResult> -where - I: IntoIterator, - S: Into> + Clone, - F: Fn(Box) -> IrOpcode, -{ - let var = RcVar::new(IrType::String, &VarVal::String("".into()), None, flags)?; - Ok(vec![ - IrOpcode::hq_cast(HqCastFields(IrType::String)), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(var.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - ] - .into_iter() - .chain( - string_source - .into_iter() - .fold( - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - fallback, - Weak::clone(project), - false, - ))), - |branch_else, string| { - let branch_if = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![instruction(string.clone().into())], - Weak::clone(project), - false, - ))); - Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(var.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_text(HqTextFields(string.into())), - IrOpcode::operator_equals, // todo: this should be a case-sensitive comparison - IrOpcode::control_if_else(ControlIfElseFields { - branch_if, - branch_else, - }), - ], - Weak::clone(project), - false, - ))) - }, - ) - .try_borrow()? - .opcodes() - .clone(), - ) - .collect()) -} - -fn from_normal_block( - block_info: &BlockInfo, - blocks: &BlockMap, - context: &StepContext, - project: &Weak, - final_next_blocks: NextBlocks, - flags: &WasmFlags, -) -> HQResult> { - let mut curr_block = Some(block_info); - let mut final_next_blocks = final_next_blocks; - let mut opcodes = vec![]; - let mut should_break = false; - while let Some(block_info) = curr_block { - opcodes.append( - &mut inputs(block_info, blocks, context, project, flags)? - .into_iter() - .chain( - #[expect( - clippy::wildcard_enum_match_arm, - reason = "too many opcodes to match individually" - )] - match &block_info.opcode { - BlockOpcode::operator_add => vec![IrOpcode::operator_add], - BlockOpcode::operator_subtract => vec![IrOpcode::operator_subtract], - BlockOpcode::operator_multiply => vec![IrOpcode::operator_multiply], - BlockOpcode::operator_divide => vec![IrOpcode::operator_divide], - BlockOpcode::operator_mod => vec![IrOpcode::operator_modulo], - BlockOpcode::motion_gotoxy => vec![IrOpcode::motion_gotoxy], - BlockOpcode::motion_setx => vec![IrOpcode::motion_setx], - BlockOpcode::motion_sety => vec![IrOpcode::motion_sety], - BlockOpcode::motion_xposition => vec![IrOpcode::motion_xposition], - BlockOpcode::motion_yposition => vec![IrOpcode::motion_yposition], - BlockOpcode::motion_changexby => vec![ - IrOpcode::motion_xposition, - IrOpcode::operator_add, - IrOpcode::motion_setx, - ], - BlockOpcode::motion_changeyby => vec![ - IrOpcode::motion_yposition, - IrOpcode::operator_add, - IrOpcode::motion_sety, - ], - BlockOpcode::motion_movesteps => vec![ - // this is a really lazy implementation but wasm-opt should optimise it - IrOpcode::hq_dup, - IrOpcode::hq_float(HqFloatFields(90.0)), - IrOpcode::motion_direction, - IrOpcode::operator_subtract, - IrOpcode::operator_cos, - IrOpcode::operator_multiply, - IrOpcode::motion_xposition, - IrOpcode::operator_add, - IrOpcode::hq_swap, - IrOpcode::hq_float(HqFloatFields(90.0)), - IrOpcode::motion_direction, - IrOpcode::operator_subtract, - IrOpcode::operator_sin, - IrOpcode::operator_multiply, - IrOpcode::motion_yposition, - IrOpcode::operator_add, - IrOpcode::motion_gotoxy, - ], - BlockOpcode::motion_direction => vec![IrOpcode::motion_direction], - BlockOpcode::motion_pointindirection => { - vec![IrOpcode::motion_pointindirection] - } - BlockOpcode::motion_turnright => vec![ - IrOpcode::motion_direction, - IrOpcode::operator_add, - IrOpcode::motion_pointindirection, - ], - BlockOpcode::motion_turnleft => vec![ - IrOpcode::motion_direction, - IrOpcode::operator_subtract, - IrOpcode::hq_integer(HqIntegerFields(-1)), - IrOpcode::operator_multiply, - IrOpcode::motion_pointindirection, - ], - BlockOpcode::looks_say => vec![IrOpcode::looks_say(LooksSayFields { - debug: context.debug, - target_idx: context.target().index(), - })], - BlockOpcode::looks_think => vec![IrOpcode::looks_think(LooksThinkFields { - debug: context.debug, - target_idx: context.target().index(), - })], - BlockOpcode::operator_join => vec![IrOpcode::operator_join], - BlockOpcode::operator_length => vec![IrOpcode::operator_length], - BlockOpcode::operator_contains => vec![IrOpcode::operator_contains], - BlockOpcode::operator_letter_of => vec![IrOpcode::operator_letter_of], - BlockOpcode::sensing_dayssince2000 => vec![IrOpcode::sensing_dayssince2000], - BlockOpcode::sensing_keypressed => vec![IrOpcode::sensing_keypressed], - BlockOpcode::sensing_keyoptions => { - let (sb3::Field::Value((Some(val),)) - | sb3::Field::ValueId(Some(val), _)) = - block_info.fields.get("KEY_OPTION").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field KEY_OPTION" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing value for KEY_OPTION field" - ) - }; - let VarVal::String(key_option) = val else { - hq_bad_proj!( - "invalid project.json - non-string value for KEY_OPTION field" - ) - }; - vec![IrOpcode::hq_text(HqTextFields(key_option.clone()))] - } - BlockOpcode::sensing_timer => vec![IrOpcode::sensing_timer], - BlockOpcode::sensing_mousex => vec![IrOpcode::sensing_mousex], - BlockOpcode::sensing_mousey => vec![IrOpcode::sensing_mousey], - BlockOpcode::sensing_mousedown => vec![IrOpcode::sensing_mousedown], - BlockOpcode::sensing_answer => vec![IrOpcode::sensing_answer], - BlockOpcode::sensing_resettimer => vec![IrOpcode::sensing_reset_timer], - BlockOpcode::operator_lt => vec![IrOpcode::operator_lt], - BlockOpcode::operator_gt => vec![IrOpcode::operator_gt], - BlockOpcode::operator_equals => vec![IrOpcode::operator_equals], - BlockOpcode::operator_not => vec![IrOpcode::operator_not], - BlockOpcode::operator_and => vec![IrOpcode::operator_and], - BlockOpcode::operator_or => vec![IrOpcode::operator_or], - BlockOpcode::operator_round => vec![IrOpcode::operator_round], - BlockOpcode::operator_mathop => { - let (sb3::Field::Value((Some(val),)) - | sb3::Field::ValueId(Some(val), _)) = - block_info.fields.get("OPERATOR").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field OPERATOR" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing value for OPERATOR field" - ) - }; - let VarVal::String(operator) = val else { - hq_bad_proj!( - "invalid project.json - non-string value for OPERATOR field" - ) - }; - match operator.to_lowercase().as_str() { - "abs" => vec![IrOpcode::operator_abs], - "floor" => vec![IrOpcode::operator_floor], - "ceiling" => vec![IrOpcode::operator_ceiling], - "sqrt" => vec![IrOpcode::operator_sqrt], - "sin" => vec![IrOpcode::operator_sin], - "cos" => vec![IrOpcode::operator_cos], - "tan" => vec![IrOpcode::operator_tan], - "asin" => vec![IrOpcode::operator_asin], - "acos" => vec![IrOpcode::operator_acos], - "atan" => vec![IrOpcode::operator_atan], - "ln" => vec![IrOpcode::operator_ln], - "log" => vec![IrOpcode::operator_log], - "e ^" => vec![IrOpcode::operator_exp], - "10 ^" => vec![IrOpcode::operator_pow10], - other => hq_bad_proj!("unknown mathop {}", other), - } - } - BlockOpcode::operator_random => vec![IrOpcode::operator_random], - BlockOpcode::event_broadcast_menu => { - let sb3::Field::ValueId(val, _id) = - block_info.fields.get("BROADCAST_OPTION").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field BROADCAST_OPTION" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing broadcast name for \ - BROADCAST_OPTION field" - ); - }; - let VarVal::String(name) = val.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null broadcast name for \ - BROADCAST_OPTION field" - ) - })? - else { - hq_bad_proj!("non-string broadcast name") - }; - vec![IrOpcode::hq_text(HqTextFields(name))] - } - BlockOpcode::event_broadcast => generate_exhaustive_string_comparison( - context.project()?.broadcasts().iter().cloned(), - |broadcast| IrOpcode::event_broadcast(EventBroadcastFields(broadcast)), - vec![], - context, - project, - flags, - )?, - BlockOpcode::event_broadcastandwait => { - let poll_step = context.project()?.new_owned_step( - Step::new_poll_waiting_threads( - context.clone(), - Weak::clone(project), - ), - )?; - should_break = true; - let next_step = generate_next_step_non_inlined( - block_info, - blocks, - context, - final_next_blocks.clone(), - flags, - )?; - generate_exhaustive_string_comparison( - context.project()?.broadcasts().iter().cloned(), - |broadcast| { - IrOpcode::event_broadcast_and_wait( - EventBroadcastAndWaitFields { - broadcast, - poll_step, - next_step, - }, - ) - }, - vec![IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Schedule(next_step), - })], - context, - project, - flags, - )? - } - BlockOpcode::sensing_askandwait => { - let poll_step = - context - .project()? - .new_owned_step(Step::new_poll_waiting_event( - context.clone(), - Weak::clone(project), - ))?; - should_break = true; - if context.target().is_stage() { - let next_step = generate_next_step_non_inlined( - block_info, - blocks, - context, - final_next_blocks.clone(), - flags, - )?; - vec![IrOpcode::sensing_askandwait(SensingAskandwaitFields { - poll_step, - next_step, - })] - } else { - let next_step = generate_next_step_inlined( - block_info, - blocks, - context, - final_next_blocks.clone(), - flags, - )?; - let real_next_step = Step::new( - None, - context.clone(), - vec![ - IrOpcode::hq_text(HqTextFields("".into())), - IrOpcode::looks_say(LooksSayFields { - debug: false, - target_idx: context.target().index(), - }), - IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Inline(next_step), - }), - ], - Weak::clone(project), - true, - ) - .clone_to_non_inlined(project)?; - vec![ - IrOpcode::looks_say(LooksSayFields { - debug: false, - target_idx: context.target().index(), - }), - IrOpcode::hq_text(HqTextFields("".into())), - IrOpcode::sensing_askandwait(SensingAskandwaitFields { - poll_step, - next_step: real_next_step, - }), - ] - } - } - BlockOpcode::control_wait => { - let poll_step = context.project()?.new_owned_step( - Step::new_poll_timer(context.clone(), Weak::clone(project)), - )?; - should_break = true; - let next_step = generate_next_step_non_inlined( - block_info, - blocks, - context, - final_next_blocks.clone(), - flags, - )?; - vec![IrOpcode::control_wait(ControlWaitFields { - poll_step, - next_step, - })] - } - BlockOpcode::data_setvariableto => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("VARIABLE").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field VARIABLE" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for VARIABLE field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for VARIABLE field" - ) - })?; - let target = context.target(); - let variable = if let Some(var) = target.variables().get(&id) { - var.clone() - } else if let Some(var) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_variables() - .get(&id) - { - var.clone() - } else { - hq_bad_proj!("variable not found") - }; - *variable.is_used.try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - vec![IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(variable.var.clone()), - local_write: RefCell::new(false), - first_write: RefCell::new(false), - })] - } - BlockOpcode::data_changevariableby => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("VARIABLE").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field VARIABLE" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for VARIABLE field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for VARIABLE field" - ) - })?; - let target = context.target(); - let variable = if let Some(var) = target.variables().get(&id) { - var.clone() - } else if let Some(var) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_variables() - .get(&id) - { - var.clone() - } else { - hq_bad_proj!("variable not found") - }; - *variable.is_used.try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(variable.var.clone()), - local_read: RefCell::new(false), - }), - IrOpcode::operator_add, - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(variable.var.clone()), - local_write: RefCell::new(false), - first_write: RefCell::new(false), - }), - ] - } - BlockOpcode::data_variable => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("VARIABLE").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field VARIABLE" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for VARIABLE field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for VARIABLE field" - ) - })?; - let target = context.target(); - let variable = if let Some(var) = target.variables().get(&id) { - var.clone() - } else if let Some(var) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_variables() - .get(&id) - { - var.clone() - } else { - hq_bad_proj!("variable not found") - }; - *variable.is_used.try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - vec![IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(variable.var.clone()), - local_read: RefCell::new(false), - })] - } - BlockOpcode::data_showvariable => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("VARIABLE").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field VARIABLE" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for VARIABLE field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for VARIABLE field" - ) - })?; - let target = context.target(); - let variable = if let Some(var) = target.variables().get(&id) { - var.clone() - } else if let Some(var) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_variables() - .get(&id) - { - var.clone() - } else { - hq_bad_proj!("variable not found") - }; - *variable.is_used.try_borrow_mut()? = true; - let Some(monitor) = variable.var.monitor().as_ref() else { - hq_bad_proj!( - "tried to change visibility of variable without monitor" - ); - }; - *monitor.is_ever_visible.try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - vec![IrOpcode::data_visvariable(DataVisvariableFields { - var: RefCell::new(variable.var.clone()), - visible: true, - })] - } - BlockOpcode::data_hidevariable => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("VARIABLE").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field VARIABLE" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for VARIABLE field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for VARIABLE field" - ) - })?; - let target = context.target(); - let variable = if let Some(var) = target.variables().get(&id) { - var.clone() - } else if let Some(var) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_variables() - .get(&id) - { - var.clone() - } else { - hq_bad_proj!("variable not found") - }; - *variable.is_used.try_borrow_mut()? = true; - hq_assert!( - variable.var.monitor().is_some(), - "tried to change visibility of variable without monitor" - ); - // crate::log!("marked variable {:?} as used", id); - vec![IrOpcode::data_visvariable(DataVisvariableFields { - var: RefCell::new(variable.var.clone()), - visible: false, - })] - } - BlockOpcode::data_deletealloflist => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - *list.list.length_mutable().try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - vec![IrOpcode::data_deletealloflist(DataDeletealloflistFields { - list: list.list.clone(), - })] - } - BlockOpcode::data_addtolist => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - *list.list.length_mutable().try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - vec![IrOpcode::data_addtolist(DataAddtolistFields { - list: list.list.clone(), - })] - } - BlockOpcode::data_itemnumoflist => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - let item = RcVar::new_empty(); - let ret = RcVar::new( - IrType::IntPos.or(IrType::IntZero), - &VarVal::Int(0), - None, - flags, - )?; - let i = RcVar::new( - IrType::IntPos.or(IrType::IntZero), - &VarVal::Int(0), - None, - flags, - )?; - let condition = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(ret.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_integer(HqIntegerFields(0)), - IrOpcode::operator_equals, - IrOpcode::operator_not, - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(i.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - IrOpcode::data_teevariable(DataTeevariableFields { - var: RefCell::new(i.clone()), - local_read_write: RefCell::new(true), - }), - IrOpcode::data_lengthoflist(DataLengthoflistFields { - list: list.list.clone(), - }), - IrOpcode::operator_gt, - IrOpcode::operator_or, - ], - Weak::clone(project), - false, - ))); - let body = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(i.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::data_itemoflist(DataItemoflistFields { - list: list.list.clone(), - }), - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(item.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::operator_equals, - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(i.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::operator_multiply, - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(ret.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(false), - }), - ], - Weak::clone(project), - false, - ))); - vec![ - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(item.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - IrOpcode::hq_integer(HqIntegerFields(0)), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(i.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - IrOpcode::hq_integer(HqIntegerFields(0)), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(ret.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - IrOpcode::control_loop(ControlLoopFields { - first_condition: None, - condition, - body, - pre_body: None, - flip_if: true, - }), - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(ret.clone()), - local_read: RefCell::new(true), - }), - ] - } - BlockOpcode::data_listcontainsitem => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - let item = RcVar::new_empty(); - let ret = - RcVar::new(IrType::Boolean, &VarVal::Bool(false), None, flags)?; - let i = RcVar::new( - IrType::IntPos.or(IrType::IntZero), - &VarVal::Int(0), - None, - flags, - )?; - let condition = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(ret.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(i.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - IrOpcode::data_teevariable(DataTeevariableFields { - var: RefCell::new(i.clone()), - local_read_write: RefCell::new(true), - }), - IrOpcode::data_lengthoflist(DataLengthoflistFields { - list: list.list.clone(), - }), - IrOpcode::operator_gt, - IrOpcode::operator_or, - ], - Weak::clone(project), - false, - ))); - let body = Rc::new(RefCell::new(Step::new( - None, - context.clone(), - vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(i.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::data_itemoflist(DataItemoflistFields { - list: list.list.clone(), - }), - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(item.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::operator_equals, - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(ret.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(false), - }), - ], - Weak::clone(project), - false, - ))); - vec![ - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(item.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - IrOpcode::hq_integer(HqIntegerFields(0)), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(i.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - IrOpcode::hq_boolean(HqBooleanFields(false)), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(ret.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - IrOpcode::control_loop(ControlLoopFields { - first_condition: None, - condition, - body, - pre_body: None, - flip_if: true, - }), - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(ret.clone()), - local_read: RefCell::new(true), - }), - ] - } - BlockOpcode::data_insertatlist => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - *list.list.length_mutable().try_borrow_mut()? = true; - - generate_list_index_op( - &list.list, - || { - IrOpcode::data_insertatlist(DataInsertatlistFields { - list: list.list.clone(), - }) - }, - None, - true, - true, - None, - context, - project, - flags, - )? - } - BlockOpcode::data_deleteoflist => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - *list.list.length_mutable().try_borrow_mut()? = true; - - generate_list_index_op( - &list.list, - || { - IrOpcode::data_deleteoflist(DataDeleteoflistFields { - list: list.list.clone(), - }) - }, - None, - false, - false, - None, - context, - project, - flags, - )? - } - BlockOpcode::data_itemoflist => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - - generate_list_index_op( - &list.list, - || { - IrOpcode::data_itemoflist(DataItemoflistFields { - list: list.list.clone(), - }) - }, - None, - false, - false, - Some(&IrOpcode::hq_text(HqTextFields("".into()))), - context, - project, - flags, - )? - } - BlockOpcode::data_lengthoflist => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - vec![IrOpcode::data_lengthoflist(DataLengthoflistFields { - list: list.list.clone(), - })] - } - BlockOpcode::data_replaceitemoflist => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - - generate_list_index_op( - &list.list, - || { - IrOpcode::data_replaceitemoflist(DataReplaceitemoflistFields { - list: list.list.clone(), - }) - }, - None, - true, - false, - None, - context, - project, - flags, - )? - } - BlockOpcode::data_listcontents => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("LIST").ok_or_else(|| { - make_hq_bad_proj!("invalid project.json - missing field LIST") - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for LIST field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for LIST field" - ) - })?; - let target = context.target(); - let list = if let Some(list) = target.lists().get(&id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(&id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - vec![IrOpcode::data_listcontents(DataListcontentsFields { - list: list.list.clone(), - })] - } - BlockOpcode::control_stop => { - let (sb3::Field::Value((Some(val),)) - | sb3::Field::ValueId(Some(val), _)) = - block_info.fields.get("STOP_OPTION").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field STOP_OPTION" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing value for STOP_OPTION field" - ) - }; - let VarVal::String(operator) = val else { - hq_bad_proj!( - "invalid project.json - non-string value for STOP_OPTION field" - ) - }; - match operator.to_lowercase().as_str() { - "all" => vec![IrOpcode::control_stop_all], - "this script" => vec![IrOpcode::hq_yield(HqYieldFields { - mode: if context.warp { - YieldMode::Return - } else { - YieldMode::None - }, - })], - "other scripts in sprite" => { - hq_todo!("control_stop other scripts in sprite") - } - other => hq_bad_proj!("unknown mathop {}", other), - } - } - - BlockOpcode::control_if => 'block: { - let BlockArrayOrId::Id(substack_id) = - match block_info.inputs.get("SUBSTACK") { - Some(input) => input, - None => break 'block vec![IrOpcode::hq_drop], - } - .get_1() - .ok_or_else(|| make_hq_bug!(""))? - .clone() - .ok_or_else(|| make_hq_bug!(""))? - else { - hq_bad_proj!("malformed SUBSTACK input") - }; - let Some(substack_block) = blocks.get(&substack_id) else { - hq_bad_proj!("SUBSTACK block doesn't seem to exist") - }; - generate_if_else( - (substack_block, substack_id), - None, - block_info, - &final_next_blocks, - blocks, - context, - &mut should_break, - flags, - )? - } - BlockOpcode::control_if_else => 'block: { - let BlockArrayOrId::Id(substack1_id) = - match block_info.inputs.get("SUBSTACK") { - Some(input) => input, - None => break 'block vec![IrOpcode::hq_drop], - } - .get_1() - .ok_or_else(|| make_hq_bug!(""))? - .clone() - .ok_or_else(|| make_hq_bug!(""))? - else { - hq_bad_proj!("malformed SUBSTACK input") - }; - let Some(substack1_block) = blocks.get(&substack1_id) else { - hq_bad_proj!("SUBSTACK block doesn't seem to exist") - }; - let BlockArrayOrId::Id(substack2_id) = - match block_info.inputs.get("SUBSTACK2") { - Some(input) => input, - None => break 'block vec![IrOpcode::hq_drop], - } - .get_1() - .ok_or_else(|| make_hq_bug!(""))? - .clone() - .ok_or_else(|| make_hq_bug!(""))? - else { - hq_bad_proj!("malformed SUBSTACK2 input") - }; - let Some(substack2_block) = blocks.get(&substack2_id) else { - hq_bad_proj!("SUBSTACK2 block doesn't seem to exist") - }; - generate_if_else( - (substack1_block, substack1_id), - Some((substack2_block, substack2_id)), - block_info, - &final_next_blocks, - blocks, - context, - &mut should_break, - flags, - )? - } - BlockOpcode::control_forever => { - let condition_instructions = - vec![IrOpcode::hq_boolean(HqBooleanFields(true))]; - let first_condition_instructions = None; - generate_loop( - context.warp, - &mut should_break, - block_info, - blocks, - context, - final_next_blocks.clone(), - first_condition_instructions, - condition_instructions, - None, - false, - vec![], - flags, - )? - } - BlockOpcode::control_repeat => { - let variable = - RcVar::new(IrType::Int, &sb3::VarVal::Int(0), None, flags)?; - let local = context.warp; - let condition_instructions = vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(variable.clone()), - local_read: RefCell::new(local), - }), - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_subtract, - IrOpcode::data_teevariable(DataTeevariableFields { - var: RefCell::new(variable.clone()), - local_read_write: RefCell::new(local), - }), - ]; - let first_condition_instructions = - Some(vec![IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(variable.clone()), - local_read: RefCell::new(local), - })]); - let setup_instructions = vec![ - IrOpcode::hq_cast(HqCastFields(IrType::Int)), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(variable), - local_write: RefCell::new(local), - first_write: RefCell::new(local), - }), - ]; - generate_loop( - context.warp, - &mut should_break, - block_info, - blocks, - context, - final_next_blocks.clone(), - first_condition_instructions, - condition_instructions, - None, - false, - setup_instructions, - flags, - )? - } - BlockOpcode::control_for_each => { - let sb3::Field::ValueId(_val, maybe_id) = - block_info.fields.get("VARIABLE").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field VARIABLE" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - missing variable id for VARIABLE field" - ); - }; - let id = maybe_id.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null variable id for VARIABLE field" - ) - })?; - let target = context.target(); - let variable = if let Some(var) = target.variables().get(&id) { - var.clone() - } else if let Some(var) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_variables() - .get(&id) - { - var.clone() - } else { - hq_bad_proj!("variable not found") - }; - *variable.is_used.try_borrow_mut()? = true; - let counter = - RcVar::new(IrType::Int, &sb3::VarVal::Int(0), None, flags)?; - let local = context.warp; - let condition_instructions = vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(counter.clone()), - local_read: RefCell::new(local), - }), - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - IrOpcode::data_teevariable(DataTeevariableFields { - var: RefCell::new(counter.clone()), - local_read_write: RefCell::new(local), - }), - ] - .into_iter() - .chain(inputs( - block_info, - blocks, - context, - &context.target().project(), - flags, - )?) - .chain(vec![IrOpcode::operator_lt]) - .collect(); - let first_condition_instructions = Some( - vec![IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(counter.clone()), - local_read: RefCell::new(local), - })] - .into_iter() - .chain(inputs( - block_info, - blocks, - context, - &context.target().project(), - flags, - )?) - .chain(vec![IrOpcode::operator_lt]) - .collect(), - ); - let setup_instructions = vec![ - IrOpcode::hq_drop, - IrOpcode::hq_integer(HqIntegerFields(0)), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(counter.clone()), - local_write: RefCell::new(local), - first_write: RefCell::new(true), - }), - ]; - let pre_body_instructions = Some(vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(counter), - local_read: RefCell::new(local), - }), - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(variable.var.clone()), - local_write: RefCell::new(false), - first_write: RefCell::new(false), - }), - ]); - generate_loop( - context.warp, - &mut should_break, - block_info, - blocks, - context, - final_next_blocks.clone(), - first_condition_instructions, - condition_instructions, - pre_body_instructions, - false, - setup_instructions, - flags, - )? - } - BlockOpcode::control_repeat_until | BlockOpcode::control_wait_until => { - let condition_instructions = inputs( - block_info, - blocks, - context, - &context.target().project(), - flags, - )?; - let first_condition_instructions = None; - let setup_instructions = vec![IrOpcode::hq_drop]; - generate_loop( - context.warp, - &mut should_break, - block_info, - blocks, - context, - final_next_blocks.clone(), - first_condition_instructions, - condition_instructions, - None, - true, - setup_instructions, - flags, - )? - } - BlockOpcode::control_while => { - let condition_instructions = inputs( - block_info, - blocks, - context, - &context.target().project(), - flags, - )?; - let first_condition_instructions = None; - let setup_instructions = vec![IrOpcode::hq_drop]; - generate_loop( - context.warp, - &mut should_break, - block_info, - blocks, - context, - final_next_blocks.clone(), - first_condition_instructions, - condition_instructions, - None, - false, - setup_instructions, - flags, - )? - } - BlockOpcode::procedures_call => 'proc_block: { - let target = context.target(); - let procs = target.procedures()?; - let serde_json::Value::String(proccode) = block_info - .mutation - .mutations - .get("proccode") - .ok_or_else(|| { - make_hq_bad_proj!("missing proccode on procedures_call") - })? - else { - hq_bad_proj!("non-string proccode on procedures_call") - }; - let Some(proc) = procs.get(proccode.as_str()) else { - break 'proc_block vec![]; - }; - let warp = context.warp || proc.always_warped(); - if warp { - proc.compile_warped(blocks, flags)?; - vec![IrOpcode::procedures_call_warp(ProceduresCallWarpFields { - proc: Rc::clone(proc), - })] - } else { - should_break = true; - let next_step = generate_next_step_non_inlined( - block_info, - blocks, - context, - final_next_blocks.clone(), - flags, - )?; - proc.compile_nonwarped(blocks, flags)?; - vec![IrOpcode::procedures_call_nonwarp( - ProceduresCallNonwarpFields { - proc: Rc::clone(proc), - next_step, - }, - )] - } - } - BlockOpcode::argument_reporter_boolean => { - procedure_argument(ProcArgType::Boolean, block_info, context)? - } - BlockOpcode::argument_reporter_string_number => { - procedure_argument(ProcArgType::StringNumber, block_info, context)? - } - BlockOpcode::looks_show => vec![ - IrOpcode::hq_boolean(HqBooleanFields(true)), - IrOpcode::looks_setvisible, - ], - BlockOpcode::looks_hide => vec![ - IrOpcode::hq_boolean(HqBooleanFields(false)), - IrOpcode::looks_setvisible, - ], - BlockOpcode::pen_clear => vec![IrOpcode::pen_clear], - BlockOpcode::pen_penDown => vec![IrOpcode::pen_pendown], - BlockOpcode::pen_penUp => vec![IrOpcode::pen_penup], - BlockOpcode::pen_setPenSizeTo => vec![IrOpcode::pen_setpensizeto], - BlockOpcode::pen_setPenColorToColor => { - vec![IrOpcode::pen_setpencolortocolor] - } - BlockOpcode::pen_changePenColorParamBy => { - vec![IrOpcode::pen_changecolorparamby] - } - BlockOpcode::pen_setPenColorParamTo => { - vec![IrOpcode::pen_setpencolorparamto] - } - BlockOpcode::pen_menu_colorParam => { - let maybe_val = - match block_info.fields.get("colorParam").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field colorParam" - ) - })? { - Field::Value((v,)) | Field::ValueId(v, _) => v, - }; - let val_varval = maybe_val.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null value for OPERATOR field" - ) - })?; - let VarVal::String(val) = val_varval else { - hq_bad_proj!( - "invalid project.json - expected colorParam field to be string" - ); - }; - vec![IrOpcode::hq_text(HqTextFields(val))] - } - BlockOpcode::looks_setsizeto => vec![IrOpcode::looks_setsizeto], - BlockOpcode::looks_size => vec![IrOpcode::looks_size], - BlockOpcode::looks_changesizeby => vec![ - IrOpcode::looks_size, - IrOpcode::operator_add, - IrOpcode::looks_setsizeto, - ], - BlockOpcode::looks_switchcostumeto => vec![IrOpcode::looks_switchcostumeto], - BlockOpcode::looks_switchbackdropto => { - vec![IrOpcode::looks_switchbackdropto] - } - BlockOpcode::looks_costumenumbername => { - let (sb3::Field::Value((val,)) | sb3::Field::ValueId(val, _)) = - block_info.fields.get("NUMBER_NAME").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field NUMBER_NAME" - ) - })?; - let sb3::VarVal::String(number_name) = - val.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null costume name for NUMBER_NAME \ - field" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - NUMBER_NAME field is not of type \ - String" - ); - }; - match &*number_name { - "number" => vec![IrOpcode::looks_costumenumber], - "name" => vec![IrOpcode::looks_costumename], - _ => hq_bad_proj!("invalid value for NUMBER_NAME field"), - } - } - BlockOpcode::looks_backdropnumbername => { - let (sb3::Field::Value((val,)) | sb3::Field::ValueId(val, _)) = - block_info.fields.get("NUMBER_NAME").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field NUMBER_NAME" - ) - })?; - let sb3::VarVal::String(number_name) = - val.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null backdrop name for \ - NUMBER_NAME field" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - NUMBER_NAME field is not of type \ - String" - ); - }; - match &*number_name { - "number" => vec![IrOpcode::looks_backdropnumber], - "name" => hq_todo!("backdrop name"), - _ => hq_bad_proj!("invalid value for NUMBER_NAME field"), - } - } - BlockOpcode::looks_backdrops => { - let (sb3::Field::Value((val,)) | sb3::Field::ValueId(val, _)) = - block_info.fields.get("BACKDROP").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field BACKDROP" - ) - })?; - let sb3::VarVal::String(backdrop_name) = - val.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null backdrop name for BACKROP \ - field" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - BACKDROP field is not of type String" - ); - }; - let backdrop_index: i32 = context - .project()? - .backdrops() - .iter() - .find_position(|costume| costume.name == backdrop_name) - .ok_or_else(|| make_hq_bug!("backdrop index not found"))? - .0 - .try_into() - .map_err(|_| make_hq_bug!("backdrop index out of bounds"))?; - vec![IrOpcode::hq_integer(HqIntegerFields(backdrop_index))] - } - BlockOpcode::looks_nextcostume => { - vec![ - IrOpcode::looks_costumenumber, - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - IrOpcode::hq_integer(HqIntegerFields( - context.target().costumes().len().try_into().map_err(|_| { - make_hq_bug!("costumes length out of bounds") - })?, - )), - IrOpcode::operator_modulo, - IrOpcode::looks_switchcostumeto, - ] - } - BlockOpcode::looks_nextbackdrop => { - vec![ - IrOpcode::looks_backdropnumber, - IrOpcode::hq_integer(HqIntegerFields(1)), - IrOpcode::operator_add, - IrOpcode::hq_integer(HqIntegerFields( - context.project()?.backdrops().len().try_into().map_err( - |_| make_hq_bug!("backdrops length out of bounds"), - )?, - )), - IrOpcode::operator_modulo, - IrOpcode::looks_switchbackdropto, - ] - } - BlockOpcode::looks_costume => { - let (sb3::Field::Value((val,)) | sb3::Field::ValueId(val, _)) = - block_info.fields.get("COSTUME").ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - missing field COSTUME" - ) - })?; - let sb3::VarVal::String(name) = val.clone().ok_or_else(|| { - make_hq_bad_proj!( - "invalid project.json - null costume name for COSTUME field" - ) - })? - else { - hq_bad_proj!( - "invalid project.json - COSTUME field is not of type String" - ); - }; - let index = context - .target() - .costumes() - .iter() - .position(|costume| costume.name == name) - .ok_or_else(|| { - make_hq_bad_proj!("missing costume with name {}", name) - })?; - vec![IrOpcode::hq_integer(HqIntegerFields( - index - .try_into() - .map_err(|_| make_hq_bug!("costume index out of bounds"))?, - ))] - } - other => hq_todo!("unimplemented block: {:?}", other), - }, - ) - .collect(), - ); - if should_break { - break; - } - curr_block = if let Some(ref next_id) = block_info.next { - let next_block = blocks - .get(next_id) - .ok_or_else(|| make_hq_bad_proj!("missing next block"))?; - // if opcodes - // .last() - // .is_some_and(super::super::instructions::IrOpcode::requests_screen_refresh) - // && !context.warp - // { - // opcodes.push(IrOpcode::hq_yield(HqYieldFields { - // mode: YieldMode::Inline(Rc::downgrade(&Step::from_block( - // next_block, - // next_id.clone(), - // blocks, - // context, - // project, - // final_next_blocks.clone(), - // true, - // flags, - // )?)), - // })); - // None - // } else { - next_block.block_info() - // } - } else if let (Some(popped_next), new_next_blocks_stack) = - final_next_blocks.clone().pop_inner() - { - match popped_next.block { - NextBlock::ID(id) => { - let next_block = blocks - .get(&id) - .ok_or_else(|| make_hq_bad_proj!("missing next block"))?; - if ( - popped_next.yield_first - // || opcodes.last().is_some_and( - // super::super::instructions::IrOpcode::requests_screen_refresh, - // ) - ) && !context.warp - { - opcodes.push(IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Schedule(Step::from_block_non_inlined( - next_block, - id.clone(), - blocks, - context, - project, - new_next_blocks_stack, - flags, - )?), - })); - None - } else { - final_next_blocks = new_next_blocks_stack; - next_block.block_info() - } - } - NextBlock::Step(mut step) => { - if ( - popped_next.yield_first - // || opcodes.last().is_some_and( - // super::super::instructions::IrOpcode::requests_screen_refresh, - // ) - ) && !context.warp - { - opcodes.push(IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Schedule(context.project()?.new_owned_step(step)?), - })); - } else { - step.make_inlined(); - opcodes.push(IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Inline(Rc::new(RefCell::new(step))), - })); - } - None - } - NextBlock::StepIndex(step_index) => { - if ( - popped_next.yield_first - // || opcodes.last().is_some_and( - // super::super::instructions::IrOpcode::requests_screen_refresh, - // ) - ) && !context.warp - { - opcodes.push(IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Schedule(step_index), - })); - } else { - let mut step = context - .project()? - .steps() - .try_borrow()? - .get(step_index.0) - .ok_or_else(|| make_hq_bug!("step index out of bounds"))? - .try_borrow()? - .clone(); - step.make_inlined(); - opcodes.push(IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Inline(Rc::new(RefCell::new(step))), - })); + })); } None } @@ -3164,187 +164,1522 @@ fn from_normal_block( Ok(opcodes.into_iter().collect()) } -static SHORTHAND_HEX_COLOUR_REGEX: Lazy = lazy_regex!(r#"^#?([a-f\d])([a-f\d])([a-f\d])$"#i); -static HEX_COLOUR_REGEX: Lazy = lazy_regex!(r#"^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$"#i); - -fn from_special_block( - block_array: &BlockArray, +fn block_to_ir( + block_info: &BlockInfo, + blocks: &BlockMap, context: &StepContext, + project: &Weak, + final_next_blocks: &NextBlocks, flags: &WasmFlags, -) -> HQResult { - Ok(match block_array { - BlockArray::NumberOrAngle(ty, value) => match ty { - // number, positive number or angle - 4 | 5 | 8 => { - // proactively convert to an integer if possible; - // if a float is needed, it will be cast at const-fold time (TODO), - // and if integers are disabled a float will be emitted anyway - if flags.integers == Switch::On && value % 1.0 == 0.0 { - #[expect( - clippy::cast_possible_truncation, - reason = "integer-ness already confirmed; `as` is saturating." - )] - IrOpcode::hq_integer(HqIntegerFields(*value as i32)) - } else { - IrOpcode::hq_float(HqFloatFields(*value)) - } + should_break: &mut bool, +) -> HQResult> { + #[expect( + clippy::wildcard_enum_match_arm, + reason = "too many opcodes to match individually" + )] + Ok(match &block_info.opcode { + BlockOpcode::operator_add => vec![IrOpcode::operator_add], + BlockOpcode::operator_subtract => vec![IrOpcode::operator_subtract], + BlockOpcode::operator_multiply => vec![IrOpcode::operator_multiply], + BlockOpcode::operator_divide => vec![IrOpcode::operator_divide], + BlockOpcode::operator_mod => vec![IrOpcode::operator_modulo], + BlockOpcode::motion_gotoxy => vec![IrOpcode::motion_gotoxy], + BlockOpcode::motion_setx => vec![IrOpcode::motion_setx], + BlockOpcode::motion_sety => vec![IrOpcode::motion_sety], + BlockOpcode::motion_xposition => vec![IrOpcode::motion_xposition], + BlockOpcode::motion_yposition => vec![IrOpcode::motion_yposition], + BlockOpcode::motion_changexby => vec![ + IrOpcode::motion_xposition, + IrOpcode::operator_add, + IrOpcode::motion_setx, + ], + BlockOpcode::motion_changeyby => vec![ + IrOpcode::motion_yposition, + IrOpcode::operator_add, + IrOpcode::motion_sety, + ], + BlockOpcode::motion_movesteps => vec![ + // this is a really lazy implementation but wasm-opt should optimise it + IrOpcode::hq_dup, + IrOpcode::hq_float(HqFloatFields(90.0)), + IrOpcode::motion_direction, + IrOpcode::operator_subtract, + IrOpcode::operator_cos, + IrOpcode::operator_multiply, + IrOpcode::motion_xposition, + IrOpcode::operator_add, + IrOpcode::hq_swap, + IrOpcode::hq_float(HqFloatFields(90.0)), + IrOpcode::motion_direction, + IrOpcode::operator_subtract, + IrOpcode::operator_sin, + IrOpcode::operator_multiply, + IrOpcode::motion_yposition, + IrOpcode::operator_add, + IrOpcode::motion_gotoxy, + ], + BlockOpcode::motion_direction => vec![IrOpcode::motion_direction], + BlockOpcode::motion_pointindirection => { + vec![IrOpcode::motion_pointindirection] + } + BlockOpcode::motion_turnright => vec![ + IrOpcode::motion_direction, + IrOpcode::operator_add, + IrOpcode::motion_pointindirection, + ], + BlockOpcode::motion_turnleft => vec![ + IrOpcode::motion_direction, + IrOpcode::operator_subtract, + IrOpcode::hq_integer(HqIntegerFields(-1)), + IrOpcode::operator_multiply, + IrOpcode::motion_pointindirection, + ], + BlockOpcode::looks_say => vec![IrOpcode::looks_say(LooksSayFields { + debug: context.debug, + target_idx: context.target().index(), + })], + BlockOpcode::looks_think => vec![IrOpcode::looks_think(LooksThinkFields { + debug: context.debug, + target_idx: context.target().index(), + })], + BlockOpcode::operator_join => vec![IrOpcode::operator_join], + BlockOpcode::operator_length => vec![IrOpcode::operator_length], + BlockOpcode::operator_contains => vec![IrOpcode::operator_contains], + BlockOpcode::operator_letter_of => vec![IrOpcode::operator_letter_of], + BlockOpcode::sensing_dayssince2000 => vec![IrOpcode::sensing_dayssince2000], + BlockOpcode::sensing_keypressed => vec![IrOpcode::sensing_keypressed], + BlockOpcode::sensing_keyoptions => { + let (Sb3Field::Value((Some(val),)) | Sb3Field::ValueId(Some(val), _)) = + block_info.fields.get("KEY_OPTION").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field KEY_OPTION") + })? + else { + hq_bad_proj!("invalid project.json - missing value for KEY_OPTION field") + }; + let VarVal::String(key_option) = val else { + hq_bad_proj!("invalid project.json - non-string value for KEY_OPTION field") + }; + vec![IrOpcode::hq_text(HqTextFields(key_option.clone()))] + } + BlockOpcode::sensing_timer => vec![IrOpcode::sensing_timer], + BlockOpcode::sensing_mousex => vec![IrOpcode::sensing_mousex], + BlockOpcode::sensing_mousey => vec![IrOpcode::sensing_mousey], + BlockOpcode::sensing_mousedown => vec![IrOpcode::sensing_mousedown], + BlockOpcode::sensing_answer => vec![IrOpcode::sensing_answer], + BlockOpcode::sensing_resettimer => vec![IrOpcode::sensing_reset_timer], + BlockOpcode::operator_lt => vec![IrOpcode::operator_lt], + BlockOpcode::operator_gt => vec![IrOpcode::operator_gt], + BlockOpcode::operator_equals => vec![IrOpcode::operator_equals], + BlockOpcode::operator_not => vec![IrOpcode::operator_not], + BlockOpcode::operator_and => vec![IrOpcode::operator_and], + BlockOpcode::operator_or => vec![IrOpcode::operator_or], + BlockOpcode::operator_round => vec![IrOpcode::operator_round], + BlockOpcode::operator_mathop => { + let (Sb3Field::Value((Some(val),)) | Sb3Field::ValueId(Some(val), _)) = + block_info.fields.get("OPERATOR").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field OPERATOR") + })? + else { + hq_bad_proj!("invalid project.json - missing value for OPERATOR field") + }; + let VarVal::String(operator) = val else { + hq_bad_proj!("invalid project.json - non-string value for OPERATOR field") + }; + match operator.to_lowercase().as_str() { + "abs" => vec![IrOpcode::operator_abs], + "floor" => vec![IrOpcode::operator_floor], + "ceiling" => vec![IrOpcode::operator_ceiling], + "sqrt" => vec![IrOpcode::operator_sqrt], + "sin" => vec![IrOpcode::operator_sin], + "cos" => vec![IrOpcode::operator_cos], + "tan" => vec![IrOpcode::operator_tan], + "asin" => vec![IrOpcode::operator_asin], + "acos" => vec![IrOpcode::operator_acos], + "atan" => vec![IrOpcode::operator_atan], + "ln" => vec![IrOpcode::operator_ln], + "log" => vec![IrOpcode::operator_log], + "e ^" => vec![IrOpcode::operator_exp], + "10 ^" => vec![IrOpcode::operator_pow10], + other => hq_bad_proj!("unknown mathop {}", other), } - // positive integer, integer - 6 | 7 => { - hq_assert!( - value % 1.0 == 0.0, - "inputs of integer or positive integer types should be integers" + } + BlockOpcode::operator_random => vec![IrOpcode::operator_random], + BlockOpcode::event_broadcast_menu => { + let Sb3Field::ValueId(val, _id) = + block_info.fields.get("BROADCAST_OPTION").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field BROADCAST_OPTION") + })? + else { + hq_bad_proj!( + "invalid project.json - missing broadcast name for BROADCAST_OPTION field" ); - #[expect( - clippy::cast_possible_truncation, - reason = "integer-ness already confirmed; `as` is saturating." - )] - if flags.integers == Switch::On { - IrOpcode::hq_integer(HqIntegerFields(*value as i32)) - } else { - IrOpcode::hq_float(HqFloatFields(*value)) - } - } - // string - 10 => IrOpcode::hq_text(HqTextFields(value.to_string().into_boxed_str())), - _ => hq_bad_proj!("bad project json (block array of type ({}, f64))", ty), - }, - // a string input should really be a colour or a string, but often numbers - // are serialised as strings in the project.json - BlockArray::ColorOrString(ty, value) => match ty { - // number, positive number or integer - 4 | 5 | 8 => { - if let Ok(float) = value.parse() { - // proactively convert to an integer if possible; - // if a float is needed, it will be cast at const-fold time (TODO), - // and if integers are disabled a float will be emitted anyway - if flags.integers == Switch::On && float % 1.0 == 0.0 { - #[expect( - clippy::cast_possible_truncation, - reason = "integer-ness already confirmed; `as` is saturating." - )] - IrOpcode::hq_integer(HqIntegerFields(float as i32)) - } else { - IrOpcode::hq_float(HqFloatFields(float)) - } - } else { - IrOpcode::hq_text(HqTextFields(value.clone())) - } - } - // integer, positive integer - 6 | 7 => - { - #[expect( - clippy::same_functions_in_if_condition, - reason = "false positive; called with different generic args" - )] - if flags.integers == Switch::On { - if let Ok(int) = value.parse() { - IrOpcode::hq_integer(HqIntegerFields(int)) - } else if let Ok(float) = value.parse() { - IrOpcode::hq_float(HqFloatFields(float)) - } else { - IrOpcode::hq_text(HqTextFields(value.clone())) - } - } else if let Ok(float) = value.parse() { - IrOpcode::hq_float(HqFloatFields(float)) - } else { - IrOpcode::hq_text(HqTextFields(value.clone())) - } - } - // colour - 9 => { - let hex = (*SHORTHAND_HEX_COLOUR_REGEX).replace(value, "$1$1$2$2$3$3"); - if let Some(captures) = (*HEX_COLOUR_REGEX).captures(&hex) { - if let box [r, g, b] = (1..4) - .map(|i| &captures[i]) - .map(|capture| { - u8::from_str_radix(capture, 16) - .map_err(|_| make_hq_bug!("hex substring out of u8 bounds")) - }) - .collect::>>()? - { - IrOpcode::hq_color_rgb(HqColorRgbFields { r, g, b }) - } else { - IrOpcode::hq_color_rgb(HqColorRgbFields { r: 0, g: 0, b: 0 }) - } - } else { - IrOpcode::hq_color_rgb(HqColorRgbFields { r: 0, g: 0, b: 0 }) - } - } - // string - 10 => 'textBlock: { - if flags.eager_number_parsing == Switch::On - && let Ok(float) = value.parse::() - && *float.to_string() == **value - { - break 'textBlock if flags.integers == Switch::On && float % 1.0 == 0.0 { - #[expect( - clippy::cast_possible_truncation, - reason = "integer-ness already confirmed; `as` is saturating." - )] - IrOpcode::hq_integer(HqIntegerFields(float as i32)) - } else { - IrOpcode::hq_float(HqFloatFields(float)) - }; - } - IrOpcode::hq_text(HqTextFields(value.clone())) - } - _ => hq_bad_proj!("bad project json (block array of type ({}, string))", ty), - }, - BlockArray::Broadcast(ty, name, id) | BlockArray::VariableOrList(ty, name, id, _, _) => { - match ty { - 11 => IrOpcode::hq_text(HqTextFields(name.clone())), - 12 => { - let target = context.target(); - let variable = if let Some(var) = target.variables().get(id) { - var.clone() - } else if let Some(var) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_variables() - .get(id) - { - var.clone() - } else { - hq_bad_proj!("variable not found") - }; - *variable.is_used.try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(variable.var.clone()), - local_read: RefCell::new(false), - }) - } - 13 => { - let target = context.target(); - let list = if let Some(list) = target.lists().get(id) { - list.clone() - } else if let Some(list) = context - .target() - .project() - .upgrade() - .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? - .global_lists() - .get(id) - { - list.clone() - } else { - hq_bad_proj!("list not found") - }; - *list.is_used.try_borrow_mut()? = true; - // crate::log!("marked variable {:?} as used", id); - IrOpcode::data_listcontents(DataListcontentsFields { + }; + let VarVal::String(name) = val.clone().ok_or_else(|| { + make_hq_bad_proj!( + "invalid project.json - null broadcast name for BROADCAST_OPTION field" + ) + })? + else { + hq_bad_proj!("non-string broadcast name") + }; + vec![IrOpcode::hq_text(HqTextFields(name))] + } + BlockOpcode::event_broadcast => generate_exhaustive_string_comparison( + context.project()?.broadcasts().iter().cloned(), + |broadcast| IrOpcode::event_broadcast(EventBroadcastFields(broadcast)), + vec![], + context, + project, + flags, + )?, + BlockOpcode::event_broadcastandwait => { + let poll_step = context + .project()? + .new_owned_step(Step::new_poll_waiting_threads( + context.clone(), + Weak::clone(project), + ))?; + *should_break = true; + let next_step = generate_next_step_non_inlined( + block_info, + blocks, + context, + final_next_blocks.clone(), + flags, + )?; + generate_exhaustive_string_comparison( + context.project()?.broadcasts().iter().cloned(), + |broadcast| { + IrOpcode::event_broadcast_and_wait(EventBroadcastAndWaitFields { + broadcast, + poll_step, + next_step, + }) + }, + vec![IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Schedule(next_step), + })], + context, + project, + flags, + )? + } + BlockOpcode::sensing_askandwait => { + let poll_step = context + .project()? + .new_owned_step(Step::new_poll_waiting_event( + context.clone(), + Weak::clone(project), + ))?; + *should_break = true; + if context.target().is_stage() { + let next_step = generate_next_step_non_inlined( + block_info, + blocks, + context, + final_next_blocks.clone(), + flags, + )?; + vec![IrOpcode::sensing_askandwait(SensingAskandwaitFields { + poll_step, + next_step, + })] + } else { + let next_step = generate_next_step_inlined( + block_info, + blocks, + context, + final_next_blocks.clone(), + flags, + )?; + let real_next_step = Step::new( + None, + context.clone(), + vec![ + IrOpcode::hq_text(HqTextFields("".into())), + IrOpcode::looks_say(LooksSayFields { + debug: false, + target_idx: context.target().index(), + }), + IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Inline(next_step), + }), + ], + Weak::clone(project), + true, + ) + .clone_to_non_inlined(project)?; + vec![ + IrOpcode::looks_say(LooksSayFields { + debug: false, + target_idx: context.target().index(), + }), + IrOpcode::hq_text(HqTextFields("".into())), + IrOpcode::sensing_askandwait(SensingAskandwaitFields { + poll_step, + next_step: real_next_step, + }), + ] + } + } + BlockOpcode::control_wait => { + let poll_step = context + .project()? + .new_owned_step(Step::new_poll_timer(context.clone(), Weak::clone(project)))?; + *should_break = true; + let next_step = generate_next_step_non_inlined( + block_info, + blocks, + context, + final_next_blocks.clone(), + flags, + )?; + vec![IrOpcode::control_wait(ControlWaitFields { + poll_step, + next_step, + })] + } + BlockOpcode::data_setvariableto => { + let Sb3Field::ValueId(_val, maybe_id) = + block_info.fields.get("VARIABLE").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field VARIABLE") + })? + else { + hq_bad_proj!("invalid project.json - missing variable id for VARIABLE field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for VARIABLE field") + })?; + let target = context.target(); + let variable = if let Some(var) = target.variables().get(&id) { + var.clone() + } else if let Some(var) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_variables() + .get(&id) + { + var.clone() + } else { + hq_bad_proj!("variable not found") + }; + *variable.is_used.try_borrow_mut()? = true; + vec![IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(variable.var.clone()), + local_write: RefCell::new(false), + first_write: RefCell::new(false), + })] + } + BlockOpcode::data_changevariableby => { + let Sb3Field::ValueId(_val, maybe_id) = + block_info.fields.get("VARIABLE").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field VARIABLE") + })? + else { + hq_bad_proj!("invalid project.json - missing variable id for VARIABLE field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for VARIABLE field") + })?; + let target = context.target(); + let variable = if let Some(var) = target.variables().get(&id) { + var.clone() + } else if let Some(var) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_variables() + .get(&id) + { + var.clone() + } else { + hq_bad_proj!("variable not found") + }; + *variable.is_used.try_borrow_mut()? = true; + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(variable.var.clone()), + local_read: RefCell::new(false), + }), + IrOpcode::operator_add, + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(variable.var.clone()), + local_write: RefCell::new(false), + first_write: RefCell::new(false), + }), + ] + } + BlockOpcode::data_variable => { + let Sb3Field::ValueId(_val, maybe_id) = + block_info.fields.get("VARIABLE").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field VARIABLE") + })? + else { + hq_bad_proj!("invalid project.json - missing variable id for VARIABLE field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for VARIABLE field") + })?; + let target = context.target(); + let variable = if let Some(var) = target.variables().get(&id) { + var.clone() + } else if let Some(var) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_variables() + .get(&id) + { + var.clone() + } else { + hq_bad_proj!("variable not found") + }; + *variable.is_used.try_borrow_mut()? = true; + vec![IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(variable.var.clone()), + local_read: RefCell::new(false), + })] + } + BlockOpcode::data_showvariable => { + let Sb3Field::ValueId(_val, maybe_id) = + block_info.fields.get("VARIABLE").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field VARIABLE") + })? + else { + hq_bad_proj!("invalid project.json - missing variable id for VARIABLE field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for VARIABLE field") + })?; + let target = context.target(); + let variable = if let Some(var) = target.variables().get(&id) { + var.clone() + } else if let Some(var) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_variables() + .get(&id) + { + var.clone() + } else { + hq_bad_proj!("variable not found") + }; + *variable.is_used.try_borrow_mut()? = true; + let Some(monitor) = variable.var.monitor().as_ref() else { + hq_bad_proj!("tried to change visibility of variable without monitor"); + }; + *monitor.is_ever_visible.try_borrow_mut()? = true; + vec![IrOpcode::data_visvariable(DataVisvariableFields { + var: RefCell::new(variable.var.clone()), + visible: true, + })] + } + BlockOpcode::data_hidevariable => { + let Sb3Field::ValueId(_val, maybe_id) = + block_info.fields.get("VARIABLE").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field VARIABLE") + })? + else { + hq_bad_proj!("invalid project.json - missing variable id for VARIABLE field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for VARIABLE field") + })?; + let target = context.target(); + let variable = if let Some(var) = target.variables().get(&id) { + var.clone() + } else if let Some(var) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_variables() + .get(&id) + { + var.clone() + } else { + hq_bad_proj!("variable not found") + }; + *variable.is_used.try_borrow_mut()? = true; + hq_assert!( + variable.var.monitor().is_some(), + "tried to change visibility of variable without monitor" + ); + vec![IrOpcode::data_visvariable(DataVisvariableFields { + var: RefCell::new(variable.var.clone()), + visible: false, + })] + } + BlockOpcode::data_deletealloflist => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + *list.list.length_mutable().try_borrow_mut()? = true; + vec![IrOpcode::data_deletealloflist(DataDeletealloflistFields { + list: list.list.clone(), + })] + } + BlockOpcode::data_addtolist => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + *list.list.length_mutable().try_borrow_mut()? = true; + vec![IrOpcode::data_addtolist(DataAddtolistFields { + list: list.list.clone(), + })] + } + BlockOpcode::data_itemnumoflist => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + let item = RcVar::new_empty(); + let ret = RcVar::new( + IrType::IntPos.or(IrType::IntZero), + &VarVal::Int(0), + None, + flags, + )?; + let i = RcVar::new( + IrType::IntPos.or(IrType::IntZero), + &VarVal::Int(0), + None, + flags, + )?; + let condition = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(ret.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_integer(HqIntegerFields(0)), + IrOpcode::operator_equals, + IrOpcode::operator_not, + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(i.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + IrOpcode::data_teevariable(DataTeevariableFields { + var: RefCell::new(i.clone()), + local_read_write: RefCell::new(true), + }), + IrOpcode::data_lengthoflist(DataLengthoflistFields { + list: list.list.clone(), + }), + IrOpcode::operator_gt, + IrOpcode::operator_or, + ], + Weak::clone(project), + false, + ))); + let body = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(i.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::data_itemoflist(DataItemoflistFields { + list: list.list.clone(), + }), + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(item.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::operator_equals, + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(i.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::operator_multiply, + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(ret.clone()), + local_write: RefCell::new(true), + first_write: RefCell::new(false), + }), + ], + Weak::clone(project), + false, + ))); + vec![ + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(item), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + IrOpcode::hq_integer(HqIntegerFields(0)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(i), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + IrOpcode::hq_integer(HqIntegerFields(0)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(ret.clone()), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + IrOpcode::control_loop(ControlLoopFields { + first_condition: None, + condition, + body, + pre_body: None, + flip_if: true, + }), + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(ret), + local_read: RefCell::new(true), + }), + ] + } + BlockOpcode::data_listcontainsitem => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + let item = RcVar::new_empty(); + let ret = RcVar::new(IrType::Boolean, &VarVal::Bool(false), None, flags)?; + let i = RcVar::new( + IrType::IntPos.or(IrType::IntZero), + &VarVal::Int(0), + None, + flags, + )?; + let condition = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(ret.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(i.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + IrOpcode::data_teevariable(DataTeevariableFields { + var: RefCell::new(i.clone()), + local_read_write: RefCell::new(true), + }), + IrOpcode::data_lengthoflist(DataLengthoflistFields { + list: list.list.clone(), + }), + IrOpcode::operator_gt, + IrOpcode::operator_or, + ], + Weak::clone(project), + false, + ))); + let body = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(i.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::data_itemoflist(DataItemoflistFields { + list: list.list.clone(), + }), + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(item.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::operator_equals, + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(ret.clone()), + local_write: RefCell::new(true), + first_write: RefCell::new(false), + }), + ], + Weak::clone(project), + false, + ))); + vec![ + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(item), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + IrOpcode::hq_integer(HqIntegerFields(0)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(i), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + IrOpcode::hq_boolean(HqBooleanFields(false)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(ret.clone()), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + IrOpcode::control_loop(ControlLoopFields { + first_condition: None, + condition, + body, + pre_body: None, + flip_if: true, + }), + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(ret), + local_read: RefCell::new(true), + }), + ] + } + BlockOpcode::data_insertatlist => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + *list.list.length_mutable().try_borrow_mut()? = true; + + generate_list_index_op( + &list.list, + || { + IrOpcode::data_insertatlist(DataInsertatlistFields { list: list.list.clone(), }) + }, + None, + true, + true, + None, + context, + project, + flags, + )? + } + BlockOpcode::data_deleteoflist => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + *list.list.length_mutable().try_borrow_mut()? = true; + + generate_list_index_op( + &list.list, + || { + IrOpcode::data_deleteoflist(DataDeleteoflistFields { + list: list.list.clone(), + }) + }, + None, + false, + false, + None, + context, + project, + flags, + )? + } + BlockOpcode::data_itemoflist => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + + generate_list_index_op( + &list.list, + || { + IrOpcode::data_itemoflist(DataItemoflistFields { + list: list.list.clone(), + }) + }, + None, + false, + false, + Some(&IrOpcode::hq_text(HqTextFields("".into()))), + context, + project, + flags, + )? + } + BlockOpcode::data_lengthoflist => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + vec![IrOpcode::data_lengthoflist(DataLengthoflistFields { + list: list.list.clone(), + })] + } + BlockOpcode::data_replaceitemoflist => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + + generate_list_index_op( + &list.list, + || { + IrOpcode::data_replaceitemoflist(DataReplaceitemoflistFields { + list: list.list.clone(), + }) + }, + None, + true, + false, + None, + context, + project, + flags, + )? + } + BlockOpcode::data_listcontents => { + let Sb3Field::ValueId(_val, maybe_id) = block_info + .fields + .get("LIST") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field LIST"))? + else { + hq_bad_proj!("invalid project.json - missing variable id for LIST field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for LIST field") + })?; + let target = context.target(); + let list = if let Some(list) = target.lists().get(&id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(&id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + vec![IrOpcode::data_listcontents(DataListcontentsFields { + list: list.list.clone(), + })] + } + BlockOpcode::control_stop => { + let (Sb3Field::Value((Some(val),)) | Sb3Field::ValueId(Some(val), _)) = + block_info.fields.get("STOP_OPTION").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field STOP_OPTION") + })? + else { + hq_bad_proj!("invalid project.json - missing value for STOP_OPTION field") + }; + let VarVal::String(operator) = val else { + hq_bad_proj!("invalid project.json - non-string value for STOP_OPTION field") + }; + match operator.to_lowercase().as_str() { + "all" => vec![IrOpcode::control_stop_all], + "this script" => vec![IrOpcode::hq_yield(HqYieldFields { + mode: if context.warp { + YieldMode::Return + } else { + YieldMode::None + }, + })], + "other scripts in sprite" => { + hq_todo!("control_stop other scripts in sprite") } - _ => hq_bad_proj!( - "bad project json (block array of type ({}, string, string))", - ty - ), + other => hq_bad_proj!("unknown mathop {}", other), + } + } + + BlockOpcode::control_if => 'block: { + let BlockArrayOrId::Id(substack_id) = match block_info.inputs.get("SUBSTACK") { + Some(input) => input, + None => break 'block vec![IrOpcode::hq_drop], + } + .get_1() + .ok_or_else(|| make_hq_bug!(""))? + .clone() + .ok_or_else(|| make_hq_bug!(""))? + else { + hq_bad_proj!("malformed SUBSTACK input") + }; + let Some(substack_block) = blocks.get(&substack_id) else { + hq_bad_proj!("SUBSTACK block doesn't seem to exist") + }; + generate_if_else( + (substack_block, substack_id), + None, + block_info, + final_next_blocks, + blocks, + context, + should_break, + flags, + )? + } + BlockOpcode::control_if_else => 'block: { + let BlockArrayOrId::Id(substack1_id) = match block_info.inputs.get("SUBSTACK") { + Some(input) => input, + None => break 'block vec![IrOpcode::hq_drop], + } + .get_1() + .ok_or_else(|| make_hq_bug!(""))? + .clone() + .ok_or_else(|| make_hq_bug!(""))? + else { + hq_bad_proj!("malformed SUBSTACK input") + }; + let Some(substack1_block) = blocks.get(&substack1_id) else { + hq_bad_proj!("SUBSTACK block doesn't seem to exist") + }; + let BlockArrayOrId::Id(substack2_id) = match block_info.inputs.get("SUBSTACK2") { + Some(input) => input, + None => break 'block vec![IrOpcode::hq_drop], + } + .get_1() + .ok_or_else(|| make_hq_bug!(""))? + .clone() + .ok_or_else(|| make_hq_bug!(""))? + else { + hq_bad_proj!("malformed SUBSTACK2 input") + }; + let Some(substack2_block) = blocks.get(&substack2_id) else { + hq_bad_proj!("SUBSTACK2 block doesn't seem to exist") + }; + generate_if_else( + (substack1_block, substack1_id), + Some((substack2_block, substack2_id)), + block_info, + final_next_blocks, + blocks, + context, + should_break, + flags, + )? + } + BlockOpcode::control_forever => { + let condition_instructions = vec![IrOpcode::hq_boolean(HqBooleanFields(true))]; + let first_condition_instructions = None; + generate_loop( + context.warp, + should_break, + block_info, + blocks, + context, + final_next_blocks.clone(), + first_condition_instructions, + condition_instructions, + None, + false, + vec![], + flags, + )? + } + BlockOpcode::control_repeat => { + let variable = RcVar::new(IrType::Int, &VarVal::Int(0), None, flags)?; + let local = context.warp; + let condition_instructions = vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(variable.clone()), + local_read: RefCell::new(local), + }), + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_subtract, + IrOpcode::data_teevariable(DataTeevariableFields { + var: RefCell::new(variable.clone()), + local_read_write: RefCell::new(local), + }), + ]; + let first_condition_instructions = + Some(vec![IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(variable.clone()), + local_read: RefCell::new(local), + })]); + let setup_instructions = vec![ + IrOpcode::hq_cast(HqCastFields(IrType::Int)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(variable), + local_write: RefCell::new(local), + first_write: RefCell::new(local), + }), + ]; + generate_loop( + context.warp, + should_break, + block_info, + blocks, + context, + final_next_blocks.clone(), + first_condition_instructions, + condition_instructions, + None, + false, + setup_instructions, + flags, + )? + } + BlockOpcode::control_for_each => { + let Sb3Field::ValueId(_val, maybe_id) = + block_info.fields.get("VARIABLE").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field VARIABLE") + })? + else { + hq_bad_proj!("invalid project.json - missing variable id for VARIABLE field"); + }; + let id = maybe_id.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null variable id for VARIABLE field") + })?; + let target = context.target(); + let variable = if let Some(var) = target.variables().get(&id) { + var.clone() + } else if let Some(var) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_variables() + .get(&id) + { + var.clone() + } else { + hq_bad_proj!("variable not found") + }; + *variable.is_used.try_borrow_mut()? = true; + let counter = RcVar::new(IrType::Int, &VarVal::Int(0), None, flags)?; + let local = context.warp; + let condition_instructions = vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(counter.clone()), + local_read: RefCell::new(local), + }), + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + IrOpcode::data_teevariable(DataTeevariableFields { + var: RefCell::new(counter.clone()), + local_read_write: RefCell::new(local), + }), + ] + .into_iter() + .chain(inputs( + block_info, + blocks, + context, + &context.target().project(), + flags, + )?) + .chain(vec![IrOpcode::operator_lt]) + .collect(); + let first_condition_instructions = Some( + vec![IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(counter.clone()), + local_read: RefCell::new(local), + })] + .into_iter() + .chain(inputs( + block_info, + blocks, + context, + &context.target().project(), + flags, + )?) + .chain(vec![IrOpcode::operator_lt]) + .collect(), + ); + let setup_instructions = vec![ + IrOpcode::hq_drop, + IrOpcode::hq_integer(HqIntegerFields(0)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(counter.clone()), + local_write: RefCell::new(local), + first_write: RefCell::new(true), + }), + ]; + let pre_body_instructions = Some(vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(counter), + local_read: RefCell::new(local), + }), + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(variable.var.clone()), + local_write: RefCell::new(false), + first_write: RefCell::new(false), + }), + ]); + generate_loop( + context.warp, + should_break, + block_info, + blocks, + context, + final_next_blocks.clone(), + first_condition_instructions, + condition_instructions, + pre_body_instructions, + false, + setup_instructions, + flags, + )? + } + BlockOpcode::control_repeat_until | BlockOpcode::control_wait_until => { + let condition_instructions = inputs( + block_info, + blocks, + context, + &context.target().project(), + flags, + )?; + let first_condition_instructions = None; + let setup_instructions = vec![IrOpcode::hq_drop]; + generate_loop( + context.warp, + should_break, + block_info, + blocks, + context, + final_next_blocks.clone(), + first_condition_instructions, + condition_instructions, + None, + true, + setup_instructions, + flags, + )? + } + BlockOpcode::control_while => { + let condition_instructions = inputs( + block_info, + blocks, + context, + &context.target().project(), + flags, + )?; + let first_condition_instructions = None; + let setup_instructions = vec![IrOpcode::hq_drop]; + generate_loop( + context.warp, + should_break, + block_info, + blocks, + context, + final_next_blocks.clone(), + first_condition_instructions, + condition_instructions, + None, + false, + setup_instructions, + flags, + )? + } + BlockOpcode::procedures_call => 'proc_block: { + let target = context.target(); + let procs = target.procedures()?; + let serde_json::Value::String(proccode) = block_info + .mutation + .mutations + .get("proccode") + .ok_or_else(|| make_hq_bad_proj!("missing proccode on procedures_call"))? + else { + hq_bad_proj!("non-string proccode on procedures_call") + }; + let Some(proc) = procs.get(proccode.as_str()) else { + break 'proc_block vec![]; + }; + let warp = context.warp || proc.always_warped(); + if warp { + proc.compile_warped(blocks, flags)?; + vec![IrOpcode::procedures_call_warp(ProceduresCallWarpFields { + proc: Rc::clone(proc), + })] + } else { + *should_break = true; + let next_step = generate_next_step_non_inlined( + block_info, + blocks, + context, + final_next_blocks.clone(), + flags, + )?; + proc.compile_nonwarped(blocks, flags)?; + vec![IrOpcode::procedures_call_nonwarp( + ProceduresCallNonwarpFields { + proc: Rc::clone(proc), + next_step, + }, + )] + } + } + BlockOpcode::argument_reporter_boolean => { + procedure_argument(ProcArgType::Boolean, block_info, context)? + } + BlockOpcode::argument_reporter_string_number => { + procedure_argument(ProcArgType::StringNumber, block_info, context)? + } + BlockOpcode::looks_show => vec![ + IrOpcode::hq_boolean(HqBooleanFields(true)), + IrOpcode::looks_setvisible, + ], + BlockOpcode::looks_hide => vec![ + IrOpcode::hq_boolean(HqBooleanFields(false)), + IrOpcode::looks_setvisible, + ], + BlockOpcode::pen_clear => vec![IrOpcode::pen_clear], + BlockOpcode::pen_penDown => vec![IrOpcode::pen_pendown], + BlockOpcode::pen_penUp => vec![IrOpcode::pen_penup], + BlockOpcode::pen_setPenSizeTo => vec![IrOpcode::pen_setpensizeto], + BlockOpcode::pen_setPenColorToColor => { + vec![IrOpcode::pen_setpencolortocolor] + } + BlockOpcode::pen_changePenColorParamBy => { + vec![IrOpcode::pen_changecolorparamby] + } + BlockOpcode::pen_setPenColorParamTo => { + vec![IrOpcode::pen_setpencolorparamto] + } + BlockOpcode::pen_menu_colorParam => { + let maybe_val = match block_info.fields.get("colorParam").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field colorParam") + })? { + Sb3Field::Value((v,)) | Sb3Field::ValueId(v, _) => v, + }; + let val_varval = maybe_val.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null value for OPERATOR field") + })?; + let VarVal::String(val) = val_varval else { + hq_bad_proj!("invalid project.json - expected colorParam field to be string"); + }; + vec![IrOpcode::hq_text(HqTextFields(val))] + } + BlockOpcode::looks_setsizeto => vec![IrOpcode::looks_setsizeto], + BlockOpcode::looks_size => vec![IrOpcode::looks_size], + BlockOpcode::looks_changesizeby => vec![ + IrOpcode::looks_size, + IrOpcode::operator_add, + IrOpcode::looks_setsizeto, + ], + BlockOpcode::looks_switchcostumeto => vec![IrOpcode::looks_switchcostumeto], + BlockOpcode::looks_switchbackdropto => { + vec![IrOpcode::looks_switchbackdropto] + } + BlockOpcode::looks_costumenumbername => { + let (Sb3Field::Value((val,)) | Sb3Field::ValueId(val, _)) = + block_info.fields.get("NUMBER_NAME").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field NUMBER_NAME") + })?; + let VarVal::String(number_name) = val.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null costume name for NUMBER_NAME field") + })? + else { + hq_bad_proj!("invalid project.json - NUMBER_NAME field is not of type String"); + }; + match &*number_name { + "number" => vec![IrOpcode::looks_costumenumber], + "name" => vec![IrOpcode::looks_costumename], + _ => hq_bad_proj!("invalid value for NUMBER_NAME field"), } } + BlockOpcode::looks_backdropnumbername => { + let (Sb3Field::Value((val,)) | Sb3Field::ValueId(val, _)) = + block_info.fields.get("NUMBER_NAME").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field NUMBER_NAME") + })?; + let VarVal::String(number_name) = val.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null backdrop name for NUMBER_NAME field") + })? + else { + hq_bad_proj!("invalid project.json - NUMBER_NAME field is not of type String"); + }; + match &*number_name { + "number" => vec![IrOpcode::looks_backdropnumber], + "name" => hq_todo!("backdrop name"), + _ => hq_bad_proj!("invalid value for NUMBER_NAME field"), + } + } + BlockOpcode::looks_backdrops => { + let (Sb3Field::Value((val,)) | Sb3Field::ValueId(val, _)) = + block_info.fields.get("BACKDROP").ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - missing field BACKDROP") + })?; + let VarVal::String(backdrop_name) = val.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null backdrop name for BACKROP field") + })? + else { + hq_bad_proj!("invalid project.json - BACKDROP field is not of type String"); + }; + let backdrop_index: i32 = context + .project()? + .backdrops() + .iter() + .find_position(|costume| costume.name == backdrop_name) + .ok_or_else(|| make_hq_bug!("backdrop index not found"))? + .0 + .try_into() + .map_err(|_| make_hq_bug!("backdrop index out of bounds"))?; + vec![IrOpcode::hq_integer(HqIntegerFields(backdrop_index))] + } + BlockOpcode::looks_nextcostume => { + vec![ + IrOpcode::looks_costumenumber, + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + IrOpcode::hq_integer(HqIntegerFields( + context + .target() + .costumes() + .len() + .try_into() + .map_err(|_| make_hq_bug!("costumes length out of bounds"))?, + )), + IrOpcode::operator_modulo, + IrOpcode::looks_switchcostumeto, + ] + } + BlockOpcode::looks_nextbackdrop => { + vec![ + IrOpcode::looks_backdropnumber, + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + IrOpcode::hq_integer(HqIntegerFields( + context + .project()? + .backdrops() + .len() + .try_into() + .map_err(|_| make_hq_bug!("backdrops length out of bounds"))?, + )), + IrOpcode::operator_modulo, + IrOpcode::looks_switchbackdropto, + ] + } + BlockOpcode::looks_costume => { + let (Sb3Field::Value((val,)) | Sb3Field::ValueId(val, _)) = block_info + .fields + .get("COSTUME") + .ok_or_else(|| make_hq_bad_proj!("invalid project.json - missing field COSTUME"))?; + let VarVal::String(name) = val.clone().ok_or_else(|| { + make_hq_bad_proj!("invalid project.json - null costume name for COSTUME field") + })? + else { + hq_bad_proj!("invalid project.json - COSTUME field is not of type String"); + }; + let index = context + .target() + .costumes() + .iter() + .position(|costume| costume.name == name) + .ok_or_else(|| make_hq_bad_proj!("missing costume with name {}", name))?; + vec![IrOpcode::hq_integer(HqIntegerFields( + index + .try_into() + .map_err(|_| make_hq_bug!("costume index out of bounds"))?, + ))] + } + other => hq_todo!("unimplemented block: {:?}", other), }) } diff --git a/src/ir/blocks/cast.rs b/src/ir/blocks/cast.rs new file mode 100644 index 00000000..16e66b18 --- /dev/null +++ b/src/ir/blocks/cast.rs @@ -0,0 +1,128 @@ +use crate::instructions::{ + DataSetvariabletoFields, DataTeevariableFields, DataVariableFields, HqCastFields, IrOpcode, +}; +use crate::ir::{ReturnType, Type as IrType}; +use crate::prelude::*; + +pub fn insert_casts( + blocks: &mut Vec, + ignore_variables: bool, + recurse: bool, +) -> HQResult<()> { + let mut type_stack: Vec<(IrType, usize)> = vec![]; // a vector of types, and where they came from + let mut casts: Vec<(usize, IrType)> = vec![]; // a vector of cast targets, and where they're needed + for (i, block) in blocks.iter().enumerate() { + let mut expected_inputs = if ignore_variables + && (matches!( + block, + IrOpcode::data_setvariableto(DataSetvariabletoFields { + local_write, .. + }) if !*local_write.borrow()) + || matches!(block, + IrOpcode::data_teevariable(DataTeevariableFields { local_read_write, .. }) if !*local_read_write.borrow()) + || matches!( + block, + IrOpcode::data_addtolist(_) | IrOpcode::data_replaceitemoflist(_) + )) { + vec![IrType::Any] + } else { + block + .acceptable_inputs()? + .iter() + .copied() + .map(|ty| if ty.is_none() { IrType::Any } else { ty }) + .collect::>() + }; + if type_stack.len() < expected_inputs.len() { + hq_bug!( + "didn't have enough inputs on the type stack\nat block {}", + block + ); + } + let actual_inputs: Vec<_> = type_stack + .splice((type_stack.len() - expected_inputs.len()).., []) + .collect(); + let mut dummy_actual_inputs: Vec<_> = actual_inputs.iter().map(|a| a.0).collect(); + for (j, (expected, actual)) in + core::iter::zip(expected_inputs.clone().into_iter(), actual_inputs).enumerate() + { + if !expected.is_none() + && !expected + .base_types() + .fold(IrType::none(), IrType::or) + .contains(actual.0) + { + if matches!( + block, + IrOpcode::data_setvariableto(_) + | IrOpcode::data_teevariable(_) + | IrOpcode::data_addtolist(_) + | IrOpcode::data_replaceitemoflist(_) + ) { + hq_bug!( + "attempted to insert a cast before a variable/list operation - variables \ + should encompass all possible types, rather than causing values to be \ + coerced. + Tried to cast from {} (at position {}) to {} (at position {}). + Occurred on these opcodes: [ + {} + ]", + actual.0, + actual.1, + expected, + i, + blocks.iter().map(|block| format!("{block}")).join(",\n"), + ) + } + casts.push((actual.1, expected)); + dummy_actual_inputs[j] = expected; + expected_inputs[j] = IrOpcode::hq_cast(HqCastFields(expected)) + .output_type(Rc::from([if actual.0.is_none() { + IrType::Any + } else { + actual.0 + }]))? + .singleton_or_else(|| { + make_hq_bug!("hq_cast returned no output type, or multiple output types") + })?; + } + } + if ignore_variables + && (matches!( + block, + IrOpcode::data_variable(DataVariableFields { + local_read, .. + }) if !*local_read.borrow()) + || matches!(block, + IrOpcode::data_teevariable(DataTeevariableFields { local_read_write, .. }) if !*local_read_write.borrow()) + || matches!( + block, + IrOpcode::data_itemoflist(_) | IrOpcode::procedures_argument(_) + )) + { + type_stack.push((IrType::Any, i)); + } else { + match block.output_type(Rc::from(dummy_actual_inputs))? { + ReturnType::Singleton(output) => type_stack.push((output, i)), + ReturnType::MultiValue(outputs) => { + type_stack.extend(outputs.iter().copied().zip(core::iter::repeat(i))); + } + ReturnType::None => (), + } + } + + if recurse && let Some(inline_steps) = block.inline_steps(false) { + for inline_step in inline_steps { + insert_casts( + inline_step.try_borrow_mut()?.opcodes_mut(), + ignore_variables, + true, + )?; + } + } + } + for (pos, ty) in casts.into_iter().rev() { + blocks.insert(pos + 1, IrOpcode::hq_cast(HqCastFields(ty))); + } + Ok(()) +} diff --git a/src/ir/blocks/control_flow.rs b/src/ir/blocks/control_flow.rs new file mode 100644 index 00000000..8a208064 --- /dev/null +++ b/src/ir/blocks/control_flow.rs @@ -0,0 +1,452 @@ +use super::{NextBlock, NextBlockInfo, NextBlocks, from_block, generate_next_step_inlined}; +use crate::instructions::{ + ControlIfElseFields, ControlLoopFields, DataSetvariabletoFields, DataVariableFields, + HqCastFields, HqTextFields, HqYieldFields, IrOpcode, YieldMode, +}; +use crate::ir::{IrProject, RcVar, Step, StepContext, Target, Type as IrType}; +use crate::prelude::*; +use crate::sb3::{Block, BlockArrayOrId, BlockInfo, Input, VarVal}; +use crate::wasm::WasmFlags; + +#[expect(clippy::too_many_arguments, reason = "too many arguments!")] +// TODO: put these arguments into a struct? +pub fn generate_loop( + warp: bool, + should_break: &mut bool, + block_info: &BlockInfo, + blocks: &BTreeMap, Block>, + context: &StepContext, + final_next_blocks: NextBlocks, + first_condition_instructions: Option>, + condition_instructions: Vec, + pre_body_instructions: Option>, + flip_if: bool, + setup_instructions: Vec, + flags: &WasmFlags, +) -> HQResult> { + let substack_id = match block_info.inputs.get("SUBSTACK") { + Some( + Input::NoShadow(_, Some(substack_input)) | Input::Shadow(_, Some(substack_input), _), + ) => { + let BlockArrayOrId::Id(id) = substack_input else { + hq_bad_proj!("malformed SUBSTACK input") + }; + Some(id) + } + _ => None, + }; + + let substack_block = if let Some(id) = substack_id { + Some( + blocks + .get(id) + .ok_or_else(|| make_hq_bad_proj!("SUBSTACK block doesn't seem to exist"))?, + ) + } else { + None + }; + if warp { + // TODO: can this be expressed in the same way as non-warping loops, + // just with yield_first: false? + let substack_blocks = if let Some(block) = substack_block { + from_block( + block, + blocks, + context, + &context.target().project(), + NextBlocks::new(false), + flags, + )? + } else { + vec![] + }; + let substack_step = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + substack_blocks, + context.target().project(), + false, + ))); + let condition_step = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + condition_instructions, + context.target().project(), + false, + ))); + let first_condition_step = first_condition_instructions.map(|instrs| { + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + instrs, + context.target().project(), + false, + ))) + }); + let pre_body_step = pre_body_instructions.map(|instrs| { + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + instrs, + context.target().project(), + false, + ))) + }); + Ok(setup_instructions + .into_iter() + .chain(vec![IrOpcode::control_loop(ControlLoopFields { + first_condition: first_condition_step, + condition: condition_step, + body: substack_step, + pre_body: pre_body_step, + flip_if, + })]) + .collect()) + } else { + *should_break = true; + let next_step = + generate_next_step_inlined(block_info, blocks, context, final_next_blocks, flags)?; + let project = context.project()?; + let mut condition_step = Step::new( + None, + context.clone(), + condition_instructions.clone(), + context.target().project(), + true, + ); + let substack_step = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![], + context.target().project(), + false, + ))); + condition_step + .opcodes_mut() + .push(IrOpcode::control_if_else(ControlIfElseFields { + branch_if: Rc::clone(if flip_if { &next_step } else { &substack_step }), + branch_else: Rc::clone(if flip_if { &substack_step } else { &next_step }), + })); + let condition_step_index = project.new_owned_step(condition_step)?; + if let Some(pre_body_blocks) = pre_body_instructions { + substack_step + .try_borrow_mut()? + .opcodes_mut() + .extend(pre_body_blocks); + } + if let Some(block) = substack_block { + let substack_blocks = from_block( + block, + blocks, + context, + &context.target().project(), + NextBlocks::new(false).extend_with_inner(NextBlockInfo { + yield_first: true, + block: NextBlock::StepIndex(condition_step_index), + }), + flags, + )?; + substack_step + .try_borrow_mut()? + .opcodes_mut() + .extend(substack_blocks); + } else { + substack_step + .try_borrow_mut()? + .opcodes_mut() + .push(IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Schedule(condition_step_index), + })); + } + Ok(setup_instructions + .into_iter() + .chain(first_condition_instructions.map_or(condition_instructions, |instrs| instrs)) + .chain(vec![IrOpcode::control_if_else(ControlIfElseFields { + branch_if: Rc::clone(if flip_if { &next_step } else { &substack_step }), + branch_else: Rc::clone(if flip_if { &substack_step } else { &next_step }), + })]) + .collect()) + } +} + +#[expect(clippy::too_many_arguments, reason = "will fix later. maybe.")] +pub fn generate_if_else( + if_block: (&Block, Box), + maybe_else_block: Option<(&Block, Box)>, + block_info: &BlockInfo, + final_next_blocks: &NextBlocks, + blocks: &BTreeMap, Block>, + context: &StepContext, + should_break: &mut bool, + flags: &WasmFlags, +) -> HQResult> { + if !context.warp { + let this_project = context.project()?; + let dummy_project = Rc::new(IrProject::new( + this_project.global_variables().clone(), + this_project.global_lists().clone(), + Box::from(this_project.broadcasts()), + 0, + vec![], + )); + let dummy_target = Rc::new(Target::new( + false, + context.target().variables().clone(), + context.target().lists().clone(), + Rc::downgrade(&dummy_project), + RefCell::new(context.target().procedures()?.clone()), + 0, + context.target().costumes().into(), + )); + dummy_project + .targets() + .try_borrow_mut() + .map_err(|_| make_hq_bug!("couldn't mutably borrow cell"))? + .insert("".into(), Rc::clone(&dummy_target)); + let dummy_context = StepContext { + target: Rc::clone(&dummy_target), + ..context.clone() + }; + let dummy_if_step = Step::from_block( + if_block.0, + if_block.1.clone(), + blocks, + &dummy_context, + &Rc::downgrade(&dummy_project), + NextBlocks::new(false), + false, + flags, + )?; + let dummy_else_step = if let Some((else_block, else_block_id)) = maybe_else_block.clone() { + Step::from_block( + else_block, + else_block_id, + blocks, + &dummy_context, + &Rc::downgrade(&dummy_project), + NextBlocks::new(false), + false, + flags, + )? + } else { + Step::new_empty( + Rc::downgrade(&dummy_project), + false, + Rc::clone(&dummy_target), + ) + }; + if dummy_if_step.does_yield() || dummy_else_step.does_yield() { + // TODO: ideally if only one branch yields then we'd duplicate the next step and put one + // version inline after the branch, and the other tagged on in the substep's NextBlocks + // as usual, to allow for extra variable type optimisations. + #[expect( + clippy::option_if_let_else, + reason = "map_or_else alternative is too complex" + )] + let (next_block, next_blocks) = if let Some(ref next_block) = block_info.next { + ( + Some(NextBlock::ID(next_block.clone())), + final_next_blocks.extend_with_inner(NextBlockInfo { + yield_first: false, + block: NextBlock::ID(next_block.clone()), + }), + ) + } else if let (Some(next_block_info), _) = final_next_blocks.clone().pop_inner() { + (Some(next_block_info.block), final_next_blocks.clone()) + } else { + ( + None, + final_next_blocks.clone(), // preserve termination behaviour + ) + }; + let final_if_step = { + Step::from_block( + if_block.0, + if_block.1, + blocks, + context, + &context.target().project(), + next_blocks.clone(), + false, + flags, + )? + }; + let final_else_step = if let Some((else_block, else_block_id)) = maybe_else_block { + Step::from_block( + else_block, + else_block_id, + blocks, + context, + &context.target().project(), + next_blocks, + false, + flags, + )? + } else { + let opcode = match next_block { + Some(NextBlock::ID(id)) => { + let next_block = blocks + .get(&id) + .ok_or_else(|| make_hq_bad_proj!("missing next block"))?; + vec![IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Inline(Rc::new(RefCell::new(Step::from_block( + next_block, + id.clone(), + blocks, + context, + &context.target().project(), + next_blocks, + false, + flags, + )?))), + })] + } + Some(NextBlock::Step(mut step)) => { + step.make_inlined(); + vec![IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Inline(Rc::new(RefCell::new(step))), + })] + } + Some(NextBlock::StepIndex(step_index)) => { + let mut step = context + .project()? + .steps() + .try_borrow()? + .get(step_index.0) + .ok_or_else(|| make_hq_bug!("step index out of bounds"))? + .try_borrow()? + .clone(); + step.make_inlined(); + vec![IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Inline(Rc::new(RefCell::new(step))), + })] + } + None => { + if next_blocks.terminating() { + vec![IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::None, + })] + } else { + vec![] + } + } + }; + Step::new( + None, + context.clone(), + opcode, + context.target().project(), + false, + ) + }; + *should_break = true; + return Ok(vec![IrOpcode::control_if_else(ControlIfElseFields { + branch_if: Rc::new(RefCell::new(final_if_step)), + branch_else: Rc::new(RefCell::new(final_else_step)), + })]); + } + } + let final_if_step = Step::from_block( + if_block.0, + if_block.1, + blocks, + context, + &context.target().project(), + NextBlocks::new(false), + false, + flags, + )?; + let final_else_step = if let Some((else_block, else_block_id)) = maybe_else_block { + Step::from_block( + else_block, + else_block_id, + blocks, + context, + &context.target().project(), + NextBlocks::new(false), + false, + flags, + )? + } else { + Step::new( + None, + context.clone(), + vec![], + context.target().project(), + false, + ) + }; + Ok(vec![IrOpcode::control_if_else(ControlIfElseFields { + branch_if: Rc::new(RefCell::new(final_if_step)), + branch_else: Rc::new(RefCell::new(final_else_step)), + })]) +} + +pub fn generate_exhaustive_string_comparison( + string_source: I, + instruction: F, + fallback: Vec, + context: &StepContext, + project: &Weak, + flags: &WasmFlags, +) -> HQResult> +where + I: IntoIterator, + S: Into> + Clone, + F: Fn(Box) -> IrOpcode, +{ + let var = RcVar::new(IrType::String, &VarVal::String("".into()), None, flags)?; + Ok(vec![ + IrOpcode::hq_cast(HqCastFields(IrType::String)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(var.clone()), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + ] + .into_iter() + .chain( + string_source + .into_iter() + .fold( + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + fallback, + Weak::clone(project), + false, + ))), + |branch_else, string| { + let branch_if = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![instruction(string.clone().into())], + Weak::clone(project), + false, + ))); + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_text(HqTextFields(string.into())), + IrOpcode::operator_equals, // todo: this should be a case-sensitive comparison + IrOpcode::control_if_else(ControlIfElseFields { + branch_if, + branch_else, + }), + ], + Weak::clone(project), + false, + ))) + }, + ) + .try_borrow()? + .opcodes() + .clone(), + ) + .collect()) +} diff --git a/src/ir/blocks/inputs.rs b/src/ir/blocks/inputs.rs new file mode 100644 index 00000000..2746cadf --- /dev/null +++ b/src/ir/blocks/inputs.rs @@ -0,0 +1,184 @@ +use super::special::from_special_block; +use super::{NextBlocks, from_block}; +use crate::instructions::IrOpcode; +use crate::ir::{IrProject, StepContext}; +use crate::prelude::*; +use crate::sb3::{BlockArray, BlockArrayOrId, BlockInfo, BlockMap, BlockOpcode, Input}; +use crate::wasm::WasmFlags; + +fn input_names(block_info: &BlockInfo, context: &StepContext) -> HQResult> { + let opcode = &block_info.opcode; + // target and procs need to be declared outside of the match block + // to prevent lifetime issues + let target = context.target(); + let procs = target.procedures()?; + Ok( + #[expect( + clippy::wildcard_enum_match_arm, + reason = "too many opcodes to match individually" + )] + match opcode { + BlockOpcode::looks_say | BlockOpcode::looks_think => vec!["MESSAGE"], + BlockOpcode::operator_add + | BlockOpcode::operator_divide + | BlockOpcode::operator_subtract + | BlockOpcode::operator_multiply + | BlockOpcode::operator_mod => vec!["NUM1", "NUM2"], + BlockOpcode::operator_mathop | BlockOpcode::operator_round => vec!["NUM"], + BlockOpcode::operator_lt + | BlockOpcode::operator_gt + | BlockOpcode::operator_equals + | BlockOpcode::operator_and + | BlockOpcode::operator_or => vec!["OPERAND1", "OPERAND2"], + BlockOpcode::operator_join | BlockOpcode::operator_contains => { + vec!["STRING1", "STRING2"] + } + BlockOpcode::operator_letter_of => vec!["LETTER", "STRING"], + BlockOpcode::motion_gotoxy => vec!["X", "Y"], + BlockOpcode::motion_movesteps => vec!["STEPS"], + BlockOpcode::motion_pointindirection => vec!["DIRECTION"], + BlockOpcode::motion_turnleft | BlockOpcode::motion_turnright => vec!["DEGREES"], + BlockOpcode::sensing_keypressed => vec!["KEY_OPTION"], + BlockOpcode::sensing_dayssince2000 + | BlockOpcode::data_variable + | BlockOpcode::argument_reporter_boolean + | BlockOpcode::argument_reporter_string_number + | BlockOpcode::looks_costume + | BlockOpcode::looks_size + | BlockOpcode::looks_nextcostume + | BlockOpcode::looks_costumenumbername + | BlockOpcode::looks_backdrops + | BlockOpcode::looks_hide + | BlockOpcode::looks_show + | BlockOpcode::pen_penDown + | BlockOpcode::pen_penUp + | BlockOpcode::pen_clear + | BlockOpcode::control_forever + | BlockOpcode::pen_menu_colorParam + | BlockOpcode::motion_direction + | BlockOpcode::data_deletealloflist + | BlockOpcode::data_lengthoflist + | BlockOpcode::data_listcontents + | BlockOpcode::control_stop + | BlockOpcode::event_broadcast_menu + | BlockOpcode::sensing_timer + | BlockOpcode::sensing_resettimer + | BlockOpcode::sensing_answer + | BlockOpcode::looks_backdropnumbername + | BlockOpcode::looks_nextbackdrop + | BlockOpcode::data_showvariable + | BlockOpcode::data_hidevariable + | BlockOpcode::sensing_mousex + | BlockOpcode::sensing_mousey + | BlockOpcode::sensing_mousedown + | BlockOpcode::motion_xposition + | BlockOpcode::motion_yposition + | BlockOpcode::sensing_keyoptions => vec![], + BlockOpcode::sensing_askandwait => vec!["QUESTION"], + BlockOpcode::event_broadcast | BlockOpcode::event_broadcastandwait => { + vec!["BROADCAST_INPUT"] + } + BlockOpcode::control_wait => vec!["DURATION"], + BlockOpcode::data_setvariableto + | BlockOpcode::data_changevariableby + | BlockOpcode::control_for_each => vec!["VALUE"], + BlockOpcode::operator_random => vec!["FROM", "TO"], + BlockOpcode::pen_setPenColorParamTo | BlockOpcode::pen_changePenColorParamBy => { + vec!["COLOR_PARAM", "VALUE"] + } + BlockOpcode::control_if + | BlockOpcode::control_if_else + | BlockOpcode::control_repeat_until + | BlockOpcode::control_while + | BlockOpcode::control_wait_until => vec!["CONDITION"], + BlockOpcode::operator_not => vec!["OPERAND"], + BlockOpcode::control_repeat => vec!["TIMES"], + BlockOpcode::operator_length => vec!["STRING"], + BlockOpcode::looks_switchcostumeto => vec!["COSTUME"], + BlockOpcode::looks_switchbackdropto => vec!["BACKDROP"], + BlockOpcode::looks_setsizeto | BlockOpcode::pen_setPenSizeTo => vec!["SIZE"], + BlockOpcode::looks_changesizeby => vec!["CHANGE"], + BlockOpcode::pen_setPenColorToColor => vec!["COLOR"], + BlockOpcode::data_addtolist + | BlockOpcode::data_itemnumoflist + | BlockOpcode::data_listcontainsitem => vec!["ITEM"], + BlockOpcode::data_itemoflist | BlockOpcode::data_deleteoflist => vec!["INDEX"], + BlockOpcode::data_replaceitemoflist | BlockOpcode::data_insertatlist => { + vec!["INDEX", "ITEM"] + } + BlockOpcode::motion_changexby => vec!["DX"], + BlockOpcode::motion_changeyby => vec!["DY"], + BlockOpcode::motion_setx => vec!["X"], + BlockOpcode::motion_sety => vec!["Y"], + BlockOpcode::procedures_call => 'proc_block: { + let serde_json::Value::String(proccode) = block_info + .mutation + .mutations + .get("proccode") + .ok_or_else(|| make_hq_bad_proj!("missing proccode on procedures_call"))? + else { + hq_bad_proj!("non-string proccode on procedures_call"); + }; + let Some(proc) = procs.get(proccode.as_str()) else { + break 'proc_block vec![]; + }; + proc.arg_ids().iter().map(|b| &**b).collect() + } + other => hq_todo!("unimplemented input_names for {:?}", other), + } + .into_iter() + .map(String::from) + .collect(), + ) +} + +pub fn inputs( + block_info: &BlockInfo, + blocks: &BlockMap, + context: &StepContext, + project: &Weak, + flags: &WasmFlags, +) -> HQResult> { + Ok(input_names(block_info, context)? + .into_iter() + .map(|name| -> HQResult> { + let input = match block_info.inputs.get((*name).into()) { + Some(noshadow @ Input::NoShadow(_, Some(_))) => noshadow, + Some(shadow @ Input::Shadow(_, Some(_), _)) => shadow, + None | Some(Input::NoShadow(_, None) | Input::Shadow(_, None, _)) => { + // revert to a sensible default + &Input::NoShadow( + 0, + Some(BlockArrayOrId::Array(BlockArray::NumberOrAngle(6, 0.0))), + ) + } + }; + #[expect( + clippy::wildcard_enum_match_arm, + reason = "all variants covered in previous match guards" + )] + match input { + Input::NoShadow(_, Some(block)) | Input::Shadow(_, Some(block), _) => match block { + BlockArrayOrId::Array(arr) => { + Ok(vec![from_special_block(arr, context, flags)?]) + } + BlockArrayOrId::Id(id) => from_block( + blocks.get(id).ok_or_else(|| { + make_hq_bad_proj!("block for input {} doesn't exist", name) + })?, + blocks, + context, + project, + NextBlocks::new(false), + flags, + ), + }, + _ => hq_bad_proj!("missing input block for {}", name), + } + }) + .collect::>>()? + .iter() + .flatten() + .cloned() + .collect()) +} diff --git a/src/ir/blocks/list_op.rs b/src/ir/blocks/list_op.rs new file mode 100644 index 00000000..2fc439be --- /dev/null +++ b/src/ir/blocks/list_op.rs @@ -0,0 +1,302 @@ +use crate::instructions::{ + ControlIfElseFields, DataLengthoflistFields, DataSetvariabletoFields, DataVariableFields, + HqCastFields, HqIntegerFields, HqTextFields, IrOpcode, +}; +use crate::ir::{IrProject, RcList, RcVar, Step, StepContext, Type as IrType}; +use crate::prelude::*; +use crate::sb3::VarVal; +use crate::wasm::WasmFlags; + +pub fn generate_list_index_op( + list: &RcList, + block: B, + maybe_all_block: Option, + other_argument: bool, + add_one_to_length: bool, + default_output: Option<&IrOpcode>, + context: &StepContext, + project: &Weak, + flags: &WasmFlags, +) -> HQResult> +where + B: Fn() -> IrOpcode, +{ + let text_var = RcVar::new(IrType::String, &VarVal::String("".into()), None, flags)?; + let int_var = RcVar::new(IrType::Int, &VarVal::Int(0), None, flags)?; + let extra_var = RcVar::new_empty(); + let result_var = RcVar::new_empty(); + + let has_output = default_output.is_some(); + + let result_step = |mut opcodes: Vec| { + if has_output { + opcodes.push(IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(result_var.clone()), + local_write: RefCell::new(true), + first_write: RefCell::new(false), + })); + } + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + opcodes, + Weak::clone(project), + false, + ))) + }; + + let int_step = result_step(if other_argument { + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(int_var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_cast(HqCastFields(IrType::Int)), + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(extra_var.clone()), + local_read: RefCell::new(true), + }), + block(), + ] + } else { + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(int_var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_cast(HqCastFields(IrType::Int)), + block(), + ] + }); + + let last_step = result_step(if other_argument { + vec![IrOpcode::data_lengthoflist(DataLengthoflistFields { + list: list.clone(), + })] + .into_iter() + .chain(if add_one_to_length { + vec![ + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + ] + } else { + vec![] + }) + .chain(vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(extra_var.clone()), + local_read: RefCell::new(true), + }), + block(), + ]) + .collect() + } else { + vec![IrOpcode::data_lengthoflist(DataLengthoflistFields { + list: list.clone(), + })] + .into_iter() + .chain(if add_one_to_length { + vec![ + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + ] + } else { + vec![] + }) + .chain(vec![block()]) + .collect() + }); + + let random_step = result_step(if other_argument { + vec![ + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::data_lengthoflist(DataLengthoflistFields { list: list.clone() }), + ] + .into_iter() + .chain(if add_one_to_length { + vec![ + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + ] + } else { + vec![] + }) + .chain(vec![ + IrOpcode::operator_random, + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(extra_var.clone()), + local_read: RefCell::new(true), + }), + block(), + ]) + .collect() + } else { + vec![ + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::data_lengthoflist(DataLengthoflistFields { list: list.clone() }), + ] + .into_iter() + .chain(if add_one_to_length { + vec![ + IrOpcode::hq_integer(HqIntegerFields(1)), + IrOpcode::operator_add, + ] + } else { + vec![] + }) + .chain(vec![IrOpcode::operator_random, block()]) + .collect() + }); + + let default_step = if let Some(default_block) = default_output { + result_step(vec![default_block.clone()]) + } else { + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![], + Weak::clone(project), + false, + ))) + }; + + let not_any_step = maybe_all_block.map(|all_block| { + let all_step = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![all_block], + Weak::clone(project), + false, + ))); + + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(text_var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_text(HqTextFields("all".into())), + IrOpcode::operator_equals, + IrOpcode::control_if_else(ControlIfElseFields { + branch_if: all_step, + branch_else: Rc::clone(&default_step), + }), + ], + Weak::clone(project), + false, + ))) + }); + + let not_random_step = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(text_var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_text(HqTextFields("any".into())), + IrOpcode::operator_equals, + IrOpcode::control_if_else(ControlIfElseFields { + branch_if: Rc::clone(&random_step), + branch_else: not_any_step.unwrap_or(default_step), + }), + ], + Weak::clone(project), + false, + ))); + + let not_last_step = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(text_var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_text(HqTextFields("random".into())), + IrOpcode::operator_equals, + IrOpcode::control_if_else(ControlIfElseFields { + branch_if: random_step, + branch_else: not_random_step, + }), + ], + Weak::clone(project), + false, + ))); + + let not_int_step = Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(text_var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::hq_text(HqTextFields("last".into())), + IrOpcode::operator_equals, + IrOpcode::control_if_else(ControlIfElseFields { + branch_if: last_step, + branch_else: not_last_step, + }), + ], + Weak::clone(project), + false, + ))); + + // we do some silly shennanigans with swapping to make sure that the SSA optimiser stays happy + Ok(if other_argument { + vec![IrOpcode::hq_swap] + } else { + vec![] + } + .into_iter() + .chain(vec![ + IrOpcode::hq_dup, + IrOpcode::hq_cast(HqCastFields(IrType::String)), + IrOpcode::hq_swap, + IrOpcode::hq_cast(HqCastFields(IrType::Int)), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(int_var.clone()), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(text_var), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + ]) + .chain(if other_argument { + vec![IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(extra_var), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + })] + } else { + vec![] + }) + .chain(vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(int_var), + local_read: RefCell::new(true), + }), + IrOpcode::hq_integer(HqIntegerFields(0)), + IrOpcode::operator_gt, + IrOpcode::control_if_else(ControlIfElseFields { + branch_if: int_step, + branch_else: not_int_step, + }), + ]) + .chain(if has_output { + vec![IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(result_var), + local_read: RefCell::new(true), + })] + } else { + vec![] + }) + .collect()) +} diff --git a/src/ir/blocks/next.rs b/src/ir/blocks/next.rs new file mode 100644 index 00000000..de805149 --- /dev/null +++ b/src/ir/blocks/next.rs @@ -0,0 +1,199 @@ +use crate::instructions::{HqYieldFields, IrOpcode, YieldMode}; +use crate::ir::{InlinedStep, MaybeInlinedStep, Step, StepContext, StepIndex}; +use crate::prelude::*; +use crate::sb3::{Block, BlockInfo}; +use crate::wasm::WasmFlags; + +#[derive(Clone, Debug)] +pub enum NextBlock { + ID(Box), + Step(Step), + StepIndex(StepIndex), +} + +#[derive(Clone, Debug)] +pub struct NextBlockInfo { + pub yield_first: bool, + pub block: NextBlock, +} + +/// contains a vector of next blocks, as well as information on how to proceed when +/// there are no next blocks: true => terminate the thread, false => do nothing +/// (useful for e.g. loop bodies, or for non-stack blocks) +#[derive(Clone, Debug)] +pub struct NextBlocks(Vec, bool); + +impl NextBlocks { + pub const fn new(terminating: bool) -> Self { + Self(vec![], terminating) + } + + pub const fn terminating(&self) -> bool { + self.1 + } + + pub fn extend_with_inner(&self, new: NextBlockInfo) -> Self { + let mut cloned = self.0.clone(); + cloned.push(new); + Self(cloned, self.terminating()) + } + + pub fn pop_inner(self) -> (Option, Self) { + let terminating = self.terminating(); + let mut vec = self.0; + let popped = vec.pop(); + (popped, Self(vec, terminating)) + } +} + +/// Generates the next step to go to, based off of the block info, and the `NextBlocks` +/// passed to it. +/// +/// Returns a `MaybeInlinedStep` along with a `bool` indicating if the step should yield +/// first. +fn generate_next_step( + block_info: &BlockInfo, + blocks: &BTreeMap, Block>, + context: &StepContext, + final_next_blocks: NextBlocks, + flags: &WasmFlags, +) -> HQResult<(MaybeInlinedStep, bool)> { + let (next_block, yield_first, outer_next_blocks) = if let Some(ref next_block) = block_info.next + { + ( + Some(NextBlock::ID(next_block.clone())), + false, + final_next_blocks, + ) + } else if let (Some(next_block_info), popped_next_blocks) = + final_next_blocks.clone().pop_inner() + { + ( + Some(next_block_info.block), + next_block_info.yield_first, + popped_next_blocks, + ) + } else { + (None, false, final_next_blocks) + }; + let next_step = match next_block { + Some(NextBlock::ID(id)) => { + let Some(next_block_block) = blocks.get(&id) else { + hq_bad_proj!("next block doesn't exist") + }; + MaybeInlinedStep::Undetermined(Step::from_block( + next_block_block, + id, + blocks, + context, + &context.target().project(), + outer_next_blocks, + false, + flags, + )?) + } + Some(NextBlock::Step(step)) => MaybeInlinedStep::Undetermined(step), + Some(NextBlock::StepIndex(step_index)) => MaybeInlinedStep::NonInlined(step_index), + None => MaybeInlinedStep::Undetermined(Step::new_terminating( + context.clone(), + context.target().project(), + false, + )), + }; + Ok((next_step, yield_first)) +} + +pub fn generate_next_step_inlined( + block_info: &BlockInfo, + blocks: &BTreeMap, Block>, + context: &StepContext, + final_next_blocks: NextBlocks, + flags: &WasmFlags, +) -> HQResult { + let (next_step, yield_first) = + generate_next_step(block_info, blocks, context, final_next_blocks, flags)?; + Ok(match next_step { + MaybeInlinedStep::Inlined(step) => { + if yield_first { + let next_step_index = step + .try_borrow()? + .clone_to_non_inlined(&context.target().project())?; + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Schedule(next_step_index), + })], + context.target().project(), + false, + ))) + } else { + step + } + } + MaybeInlinedStep::NonInlined(step_index) => { + if yield_first { + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Schedule(step_index), + })], + context.target().project(), + false, + ))) + } else { + let mut step = context + .project()? + .steps() + .try_borrow()? + .get(step_index.0) + .ok_or_else(|| make_hq_bug!("step index out of bounds"))? + .try_borrow()? + .clone(); + step.make_inlined(); + Rc::new(RefCell::new(step)) + } + } + MaybeInlinedStep::Undetermined(mut step) => { + if yield_first { + let step_index = step.clone_to_non_inlined(&context.target().project())?; + Rc::new(RefCell::new(Step::new( + None, + context.clone(), + vec![IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Schedule(step_index), + })], + context.target().project(), + false, + ))) + } else { + step.make_inlined(); + Rc::new(RefCell::new(step)) + } + } + }) +} + +/// Generates a `StepIndex` for the next step of the given block, based on the +/// block info and the given `NextSteps`. The generated step must not be inlined +/// at a later stage, as it may cause reference cycles. +pub fn generate_next_step_non_inlined( + block_info: &BlockInfo, + blocks: &BTreeMap, Block>, + context: &StepContext, + final_next_blocks: NextBlocks, + flags: &WasmFlags, +) -> HQResult { + let (next_step, _yield_first) = + generate_next_step(block_info, blocks, context, final_next_blocks, flags)?; + match next_step { + MaybeInlinedStep::Inlined(step) => step + .try_borrow()? + .clone_to_non_inlined(&context.target().project()), + MaybeInlinedStep::NonInlined(step_index) => Ok(step_index), + MaybeInlinedStep::Undetermined(step) => { + step.clone_to_non_inlined(&context.target().project()) + } + } +} diff --git a/src/ir/blocks/proc_arg.rs b/src/ir/blocks/proc_arg.rs new file mode 100644 index 00000000..b9792ab4 --- /dev/null +++ b/src/ir/blocks/proc_arg.rs @@ -0,0 +1,49 @@ +use crate::instructions::{HqIntegerFields, IrOpcode, ProceduresArgumentFields}; +use crate::ir::StepContext; +use crate::prelude::*; +use crate::sb3::{BlockInfo, VarVal}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ProcArgType { + Boolean, + StringNumber, +} + +pub fn procedure_argument( + _arg_type: ProcArgType, + block_info: &BlockInfo, + context: &StepContext, +) -> HQResult> { + let Some(proc_context) = context.proc_context.clone() else { + // this is always the default, regardless of type + return Ok(vec![IrOpcode::hq_integer(HqIntegerFields(0))]); + }; + let VarVal::String(arg_name) = block_info + .fields + .get("VALUE") + .ok_or_else(|| make_hq_bad_proj!("missing VALUE field for proc argument"))? + .get_0() + .ok_or_else(|| make_hq_bad_proj!("missing value of VALUE field"))? + else { + hq_bad_proj!("non-string proc argument name") + }; + let Some(index) = proc_context + .arg_names + .iter() + .position(|name| name == arg_name) + else { + return Ok(vec![IrOpcode::hq_integer(HqIntegerFields(0))]); + }; + let arg_vars = (*proc_context.arg_vars).borrow(); + let arg_var = arg_vars + .get(index) + .ok_or_else(|| make_hq_bad_proj!("argument index not in range of argumenttypes"))?; + Ok(vec![IrOpcode::procedures_argument( + ProceduresArgumentFields { + index, + arg_var: arg_var.clone(), + in_warped: context.warp, + arg_vars: Rc::clone(&proc_context.arg_vars), + }, + )]) +} diff --git a/src/ir/blocks/special.rs b/src/ir/blocks/special.rs new file mode 100644 index 00000000..c0b27b7c --- /dev/null +++ b/src/ir/blocks/special.rs @@ -0,0 +1,198 @@ +//! Handles parsing of "special" blocks, i.e. those that are represented as arrays rather +//! than objects. + +use lazy_regex::{Lazy, lazy_regex}; +use regex::Regex; + +use crate::instructions::{ + DataListcontentsFields, DataVariableFields, HqColorRgbFields, HqFloatFields, HqIntegerFields, + HqTextFields, IrOpcode, +}; +use crate::ir::StepContext; +use crate::prelude::*; +use crate::sb3::BlockArray; +use crate::wasm::WasmFlags; +use crate::wasm::flags::Switch; + +static SHORTHAND_HEX_COLOUR_REGEX: Lazy = lazy_regex!(r#"^#?([a-f\d])([a-f\d])([a-f\d])$"#i); +static HEX_COLOUR_REGEX: Lazy = lazy_regex!(r#"^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$"#i); + +pub fn from_special_block( + block_array: &BlockArray, + context: &StepContext, + flags: &WasmFlags, +) -> HQResult { + Ok(match block_array { + BlockArray::NumberOrAngle(ty, value) => match ty { + // number, positive number or angle + 4 | 5 | 8 => { + // proactively convert to an integer if possible; + // if a float is needed, it will be cast at const-fold time (TODO), + // and if integers are disabled a float will be emitted anyway + if flags.integers == Switch::On && value % 1.0 == 0.0 { + #[expect( + clippy::cast_possible_truncation, + reason = "integer-ness already confirmed; `as` is saturating." + )] + IrOpcode::hq_integer(HqIntegerFields(*value as i32)) + } else { + IrOpcode::hq_float(HqFloatFields(*value)) + } + } + // positive integer, integer + 6 | 7 => { + hq_assert!( + value % 1.0 == 0.0, + "inputs of integer or positive integer types should be integers" + ); + #[expect( + clippy::cast_possible_truncation, + reason = "integer-ness already confirmed; `as` is saturating." + )] + if flags.integers == Switch::On { + IrOpcode::hq_integer(HqIntegerFields(*value as i32)) + } else { + IrOpcode::hq_float(HqFloatFields(*value)) + } + } + // string + 10 => IrOpcode::hq_text(HqTextFields(value.to_string().into_boxed_str())), + _ => hq_bad_proj!("bad project json (block array of type ({}, f64))", ty), + }, + // a string input should really be a colour or a string, but often numbers + // are serialised as strings in the project.json + BlockArray::ColorOrString(ty, value) => match ty { + // number, positive number or integer + 4 | 5 | 8 => { + if let Ok(float) = value.parse() { + // proactively convert to an integer if possible; + // if a float is needed, it will be cast at const-fold time (TODO), + // and if integers are disabled a float will be emitted anyway + if flags.integers == Switch::On && float % 1.0 == 0.0 { + #[expect( + clippy::cast_possible_truncation, + reason = "integer-ness already confirmed; `as` is saturating." + )] + IrOpcode::hq_integer(HqIntegerFields(float as i32)) + } else { + IrOpcode::hq_float(HqFloatFields(float)) + } + } else { + IrOpcode::hq_text(HqTextFields(value.clone())) + } + } + // integer, positive integer + 6 | 7 => + { + #[expect( + clippy::same_functions_in_if_condition, + reason = "false positive; called with different generic args" + )] + if flags.integers == Switch::On { + if let Ok(int) = value.parse() { + IrOpcode::hq_integer(HqIntegerFields(int)) + } else if let Ok(float) = value.parse() { + IrOpcode::hq_float(HqFloatFields(float)) + } else { + IrOpcode::hq_text(HqTextFields(value.clone())) + } + } else if let Ok(float) = value.parse() { + IrOpcode::hq_float(HqFloatFields(float)) + } else { + IrOpcode::hq_text(HqTextFields(value.clone())) + } + } + // colour + 9 => { + let hex = (*SHORTHAND_HEX_COLOUR_REGEX).replace(value, "$1$1$2$2$3$3"); + if let Some(captures) = (*HEX_COLOUR_REGEX).captures(&hex) { + if let box [r, g, b] = (1..4) + .map(|i| &captures[i]) + .map(|capture| { + u8::from_str_radix(capture, 16) + .map_err(|_| make_hq_bug!("hex substring out of u8 bounds")) + }) + .collect::>>()? + { + IrOpcode::hq_color_rgb(HqColorRgbFields { r, g, b }) + } else { + IrOpcode::hq_color_rgb(HqColorRgbFields { r: 0, g: 0, b: 0 }) + } + } else { + IrOpcode::hq_color_rgb(HqColorRgbFields { r: 0, g: 0, b: 0 }) + } + } + // string + 10 => 'textBlock: { + if flags.eager_number_parsing == Switch::On + && let Ok(float) = value.parse::() + && *float.to_string() == **value + { + break 'textBlock if flags.integers == Switch::On && float % 1.0 == 0.0 { + #[expect( + clippy::cast_possible_truncation, + reason = "integer-ness already confirmed; `as` is saturating." + )] + IrOpcode::hq_integer(HqIntegerFields(float as i32)) + } else { + IrOpcode::hq_float(HqFloatFields(float)) + }; + } + IrOpcode::hq_text(HqTextFields(value.clone())) + } + _ => hq_bad_proj!("bad project json (block array of type ({}, string))", ty), + }, + BlockArray::Broadcast(ty, name, id) | BlockArray::VariableOrList(ty, name, id, _, _) => { + match ty { + 11 => IrOpcode::hq_text(HqTextFields(name.clone())), + 12 => { + let target = context.target(); + let variable = if let Some(var) = target.variables().get(id) { + var.clone() + } else if let Some(var) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_variables() + .get(id) + { + var.clone() + } else { + hq_bad_proj!("variable not found") + }; + *variable.is_used.try_borrow_mut()? = true; + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(variable.var.clone()), + local_read: RefCell::new(false), + }) + } + 13 => { + let target = context.target(); + let list = if let Some(list) = target.lists().get(id) { + list.clone() + } else if let Some(list) = context + .target() + .project() + .upgrade() + .ok_or_else(|| make_hq_bug!("couldn't upgrade Weak"))? + .global_lists() + .get(id) + { + list.clone() + } else { + hq_bad_proj!("list not found") + }; + *list.is_used.try_borrow_mut()? = true; + IrOpcode::data_listcontents(DataListcontentsFields { + list: list.list.clone(), + }) + } + _ => hq_bad_proj!( + "bad project json (block array of type ({}, string, string))", + ty + ), + } + } + }) +} diff --git a/src/ir/project.rs b/src/ir/project.rs index ba439e91..7353eb2f 100644 --- a/src/ir/project.rs +++ b/src/ir/project.rs @@ -120,13 +120,10 @@ impl IrProject { let backdrops: Vec<_> = stage_target .costumes .iter() - .map(|costume| { - IrCostume { - name: costume.name.clone(), - data_format: costume.data_format, - md5ext: costume.md5ext.clone(), - //data: load_asset(costume.md5ext.as_str()), - } + .map(|costume| IrCostume { + name: costume.name.clone(), + data_format: costume.data_format, + md5ext: costume.md5ext.clone(), }) .collect(); @@ -157,13 +154,10 @@ impl IrProject { let costumes = target .costumes .iter() - .map(|costume| { - IrCostume { - name: costume.name.clone(), - data_format: costume.data_format, - md5ext: costume.md5ext.clone(), - //data: load_asset(costume.md5ext.as_str()), - } + .map(|costume| IrCostume { + name: costume.name.clone(), + data_format: costume.data_format, + md5ext: costume.md5ext.clone(), }) .collect(); let ir_target = Rc::new(Target::new( @@ -203,7 +197,6 @@ impl IrProject { .iter() .cloned() .unzip(); - // crate::log("all threads + targets created"); let threads = threads_vec.into_iter().flatten().collect::>(); *project .threads @@ -214,7 +207,6 @@ impl IrProject { .try_borrow_mut() .map_err(|_| make_hq_bug!("couldn't mutably borrow cell"))? = targets.into_iter().collect(); - // crate::log!("global_vars: {global_vars:?}"); for target in project.targets().try_borrow()?.values() { fixup_proc_types(target)?; } @@ -263,9 +255,7 @@ where if has_return { let mut step_mut = step.try_borrow_mut()?; - crate::log("borrowing opcodes mutably"); let opcodes_mut = step_mut.opcodes_mut(); - crate::log("borrowed opcodes mutably"); opcodes_mut.pop(); diff --git a/src/ir/step.rs b/src/ir/step.rs index f7e3c55b..1e7c3357 100644 --- a/src/ir/step.rs +++ b/src/ir/step.rs @@ -231,10 +231,6 @@ impl Step { .iter() .find_position(|step| RefCell::borrow(step).id == block_id) { - // crate::log( - // format!("step from_block already exists! (id: {block_id:?}); returning early") - // .as_str(), - // ); return Ok(StepIndex(existing_step)); } let step = Self::from_block( diff --git a/src/ir/variable.rs b/src/ir/variable.rs index 30243210..f004eed8 100644 --- a/src/ir/variable.rs +++ b/src/ir/variable.rs @@ -83,7 +83,6 @@ impl RcVar { impl PartialEq for RcVar { fn eq(&self, other: &Self) -> bool { self.0.id == other.0.id - // Rc::ptr_eq(self.0.get_ref(), other.0.get_ref()) } } @@ -98,13 +97,12 @@ impl PartialOrd for RcVar { impl Ord for RcVar { fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.0.id.cmp(&other.0.id) - //Rc::as_ptr(&self.0).cmp(&Rc::as_ptr(&other.0)) } } impl Hash for RcVar { fn hash(&self, state: &mut H) { - self.0.id.hash(state); //core::ptr::hash(Rc::as_ptr(&self.0), state); + self.0.id.hash(state); } } @@ -228,7 +226,6 @@ pub fn used_vars(vars: &TargetVars) -> Box<[RcVar]> { impl fmt::Display for RcVar { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let possible_types = self.0.possible_types.borrow(); - //let id = Rc::as_ptr(&self.0) as usize; let id = self.id(); write!( f, @@ -243,7 +240,6 @@ impl fmt::Display for RcVar { impl fmt::Display for RcList { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let possible_types = self.0.possible_types.borrow(); - //let id = Rc::as_ptr(&self.0) as usize; let id = self.id(); write!( f, @@ -360,7 +356,6 @@ impl RcList { impl PartialEq for RcList { fn eq(&self, other: &Self) -> bool { self.0.id == other.0.id - // Rc::ptr_eq(self.0.get_ref(), other.0.get_ref()) } } @@ -375,12 +370,11 @@ impl PartialOrd for RcList { impl Ord for RcList { fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.0.id.cmp(&other.0.id) - //Rc::as_ptr(&self.0).cmp(&Rc::as_ptr(&other.0)) } } impl Hash for RcList { fn hash(&self, state: &mut H) { - self.0.id.hash(state); //core::ptr::hash(Rc::as_ptr(&self.0), state); + self.0.id.hash(state); } } diff --git a/src/lib.rs b/src/lib.rs index 468e410b..ea706de4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,6 @@ use wasm_bindgen::prelude::*; pub mod error; pub mod ir; pub mod optimisation; -// pub mod ir_opt; pub mod sb3; pub mod wasm; #[macro_use] diff --git a/src/optimisation/ssa.rs b/src/optimisation/ssa.rs index 286f5aed..57535d49 100644 --- a/src/optimisation/ssa.rs +++ b/src/optimisation/ssa.rs @@ -89,999 +89,22 @@ reason = "implementations of Eq and Ord for RcVar and Step are independent of actual contents" )] +mod box_proc_returns; +mod type_convergence; +mod var_graph; + use alloc::collections::btree_map::Entry; use core::convert::identity; -use core::hash::{Hash, Hasher}; use core::marker::PhantomData; -use core::ops::Deref; -use petgraph::graph::{EdgeIndex, NodeIndex}; -use petgraph::stable_graph::StableDiGraph; -use petgraph::visit::EdgeRef; -use petgraph::{Incoming as EdgeIn, Outgoing as EdgeOut}; +use box_proc_returns::box_proc_returns; +use type_convergence::iterate_graphs; +use var_graph::{MaybeGraph, VarGraph, VariableMaps}; -use crate::instructions::{ - ControlIfElseFields, ControlLoopFields, DataAddtolistFields, DataInsertatlistFields, - DataItemoflistFields, DataReplaceitemoflistFields, DataSetvariabletoFields, - DataTeevariableFields, DataVariableFields, HqBoxFields, HqYieldFields, IrOpcode, - ProceduresArgumentFields, ProceduresCallNonwarpFields, ProceduresCallWarpFields, YieldMode, -}; -use crate::ir::{ - InlinedStep, IrProject, PartialStep, Proc, RcList, RcVar, ReturnType, Step, StepIndex, - Type as IrType, insert_casts, -}; +use crate::ir::{IrProject, PartialStep, Proc, RcVar, StepIndex, insert_casts}; use crate::prelude::*; use crate::wasm::flags::{Switch, VarTypeConvergence}; -#[derive(Clone, Debug)] -enum StackOperation { - Drop, - Push(VarTarget), - Pop(VarTarget), - /// this shouldn't be anything that branches or mutates variables in any way, - /// i.e. not loop, if/else, yield, set variable, etc - Opcode(IrOpcode), -} - -#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] -enum VarTarget { - Var(RcVar), - List(RcList), -} - -impl VarTarget { - fn possible_types(&self) -> core::cell::Ref<'_, IrType> { - match self { - Self::Var(var) => var.possible_types(), - Self::List(list) => list.possible_types(), - } - } - - fn add_type(&self, ty: IrType) { - match self { - Self::Var(var) => var.add_type(ty), - Self::List(list) => list.add_type(ty), - } - } -} - -/// Is this edge a backlink or not? -/// -/// We could calculate this dynamically during the reduction stage but seeing as we can know this -/// when constructing the graph, we might as well do it now. -#[derive(Copy, Clone, Debug, PartialEq)] -enum EdgeType { - Forward, - BackLink, -} - -type NodeWeight = Option; - -#[derive(Debug)] -struct BaseVarGraph { - graph: RefCell>, - exit_node: RefCell, -} - -#[derive(Clone, Debug)] -struct VarGraph(Rc); - -impl Hash for VarGraph { - fn hash(&self, state: &mut H) { - Rc::as_ptr(&self.0).hash(state); - } -} - -enum MaybeGraph { - Started, - Inlined, - Finished(VarGraph), -} - -#[derive(Clone)] -struct VariableMaps<'a> { - /// global variable -> local variable - pub ssa: BTreeMap, - /// global variable -> (arg index, arg variable) - pub proc_args: &'a BTreeMap, -} - -impl<'a> VariableMaps<'a> { - const fn new_with_proc_args(proc_args: &'a BTreeMap) -> Self { - Self { - ssa: BTreeMap::new(), - proc_args, - } - } - - fn use_current_ssa_for_global( - &self, - global_var: &RcVar, - if_ssa: S, - if_arg: A, - if_global: G, - ) -> T - where - S: FnOnce(&RcVar) -> T, - A: FnOnce(usize, &RcVar) -> T, - G: FnOnce() -> T, - { - self.ssa.get(global_var).map_or_else( - || { - if let Some((arg_index, arg_var)) = self.proc_args.get(global_var) { - if_arg(*arg_index, arg_var) - } else { - if_global() - } - }, - if_ssa, - ) - } -} - -impl VarGraph { - pub fn new() -> Self { - let mut graph = StableDiGraph::new(); - let first_node = graph.add_node(None); - let exit_node = graph.add_node(None); - graph.add_edge(first_node, exit_node, EdgeType::Forward); - Self(Rc::new(BaseVarGraph { - graph: RefCell::new(graph), - exit_node: RefCell::new(exit_node), - })) - } - - fn graph(&self) -> &RefCell> { - &self.0.graph - } - - fn exit_node(&self) -> &RefCell { - &self.0.exit_node - } - - fn add_node(&self, weight: NodeWeight) -> NodeIndex { - self.graph().borrow_mut().add_node(weight) - } - - fn add_node_at_end(&self, weight: NodeWeight) { - let new_node = self.add_node(weight); - let end_node = *self.exit_node().borrow(); - self.add_edge(end_node, new_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = new_node; - } - - fn add_edge(&self, a: NodeIndex, b: NodeIndex, weight: EdgeType) -> EdgeIndex { - self.graph().borrow_mut().add_edge(a, b, weight) - } - - /// Visits a step to split its variables and construct a variable graph for it. - fn visit_step( - &mut self, - step: S, - variable_maps: &mut VariableMaps<'_>, - graphs: &mut BTreeMap, MaybeGraph>, - next_steps: &mut Vec, - do_ssa: bool, - ) -> HQResult<()> - where - S: Deref>, - { - // crate::log!( - // "searched in graphs for step {}, got {}", - // step.id(), - // match graphs.get(step) { - // Some(MaybeGraph::Started) => "Started", - // Some(MaybeGraph::Inlined) => "Inlined", - // Some(MaybeGraph::Finished(_)) => "Finished", - // None => "None", - // } - // ); - if let Some(MaybeGraph::Inlined | MaybeGraph::Finished(_)) = - graphs.get(step.try_borrow()?.id()) - { - // we've already visited this step. - // crate::log(format!("visited step {} but it is already visited", step.id()).as_str()); - return Ok(()); - } - // crate::log(format!("visited step {}, not yet visited", step.id()).as_str()); - // crate::log!( - // "currently visited/started steps: {:?}", - // graphs.keys().map(|step| step.id()).collect::>() - // ); - // crate::log!("hash of step: {:?}", graphs.); - - let maybe_proc_context = { - let step_tmp = step.try_borrow()?; - step_tmp.context().proc_context.clone() - }; - - let mut should_propagate_ssa = - step.try_borrow()?.used_non_inline() && maybe_proc_context.is_none(); - let mut step_ended_on_stop = false; - - let mut opcode_replacements: Vec<(usize, IrOpcode)> = vec![]; - let mut additional_opcodes: Vec<(usize, Vec)> = vec![]; - 'opcode_loop: for (i, opcode) in step.try_borrow()?.opcodes().iter().enumerate() { - // crate::log!("opcode: {opcode:?}"); - // let's just assume that input types match up. - #[expect(clippy::wildcard_enum_match_arm, reason = "too many variants to match")] - match opcode { - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var, - local_write: locality, - first_write, - }) => { - // crate::log!("found a variable write operation, type stack: {type_stack:?}"); - let already_local = *locality.try_borrow()?; - if already_local { - if var.try_borrow()?.possible_types().is_none() { - let pop_node = self.add_node(Some(StackOperation::Pop( - VarTarget::Var(var.try_borrow()?.clone()), - ))); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, pop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = pop_node; - } else { - let drop_node = self.add_node(Some(StackOperation::Drop)); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, drop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = drop_node; - } - continue 'opcode_loop; - } - if !do_ssa { - let pop_node = self.add_node(Some(StackOperation::Pop(VarTarget::Var( - var.try_borrow()?.clone(), - )))); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, pop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = pop_node; - continue 'opcode_loop; - } - let new_variable = RcVar::new_empty(); - { - variable_maps - .ssa - .insert(var.try_borrow()?.clone(), new_variable.clone()); - } - *var.try_borrow_mut()? = new_variable; - *first_write.try_borrow_mut()? = true; - *locality.try_borrow_mut()? = true; - let pop_node = self.add_node(Some(StackOperation::Pop(VarTarget::Var( - var.try_borrow()?.clone(), - )))); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, pop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = pop_node; - } - IrOpcode::data_teevariable(DataTeevariableFields { - var, - local_read_write: locality, - }) => { - // crate::log("found a variable tee operation"); - let already_local = *locality.try_borrow()?; - if !do_ssa || already_local { - self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( - var.try_borrow()?.clone(), - )))); - continue 'opcode_loop; - } - let new_variable = RcVar::new_empty(); - { - variable_maps - .ssa - .insert(var.try_borrow()?.clone(), new_variable.clone()); - } - *var.try_borrow_mut()? = new_variable; - *locality.try_borrow_mut()? = true; - self.add_node_at_end(Some(StackOperation::Pop(VarTarget::Var( - var.try_borrow()?.clone(), - )))); - self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( - var.try_borrow()?.clone(), - )))); - } - IrOpcode::data_variable(DataVariableFields { var, local_read }) => { - // crate::log("found a variable read operation"); - if *local_read.try_borrow()? { - self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( - var.try_borrow()?.clone(), - )))); - // crate::log("variable is already local; skipping"); - continue; - } - let gvar = &var.try_borrow()?.clone(); - let mut var_mut = var.try_borrow_mut()?; - self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( - variable_maps.use_current_ssa_for_global::<_, _, _, HQResult<_>>( - gvar, - |current_ssa_var| { - *var_mut = current_ssa_var.clone(); - *local_read.try_borrow_mut()? = true; - Ok(current_ssa_var.clone()) - }, - |var_index, arg_var| { - opcode_replacements.push(( - i, - IrOpcode::procedures_argument(ProceduresArgumentFields { - index: var_index, - arg_var: arg_var.clone(), - in_warped: true, - arg_vars: Rc::clone( - &maybe_proc_context - .as_ref() - .ok_or_else(|| { - make_hq_bug!( - "tried to access proc argument when proc \ - context was None" - ) - })? - .arg_vars, - ), - }), - )); - Ok(arg_var.clone()) - }, - || Ok(gvar.clone()), - )?, - )))); - } - IrOpcode::data_itemoflist(DataItemoflistFields { list }) => { - self.add_node_at_end(Some(StackOperation::Drop)); - self.add_node_at_end(Some(StackOperation::Push(VarTarget::List(list.clone())))); - } - IrOpcode::data_addtolist(DataAddtolistFields { list }) => { - self.add_node_at_end(Some(StackOperation::Pop(VarTarget::List(list.clone())))); - } - IrOpcode::data_replaceitemoflist(DataReplaceitemoflistFields { list }) - | IrOpcode::data_insertatlist(DataInsertatlistFields { list }) => { - self.add_node_at_end(Some(StackOperation::Pop(VarTarget::List(list.clone())))); - self.add_node_at_end(Some(StackOperation::Drop)); - } - IrOpcode::control_if_else(ControlIfElseFields { - branch_if, - branch_else, - }) => { - // crate::log("found control_if_else"); - // the top item on the stack should be consumed by control_if_else - self.add_node_at_end(Some(StackOperation::Drop)); - let last_node = *self.exit_node().borrow(); - - let branch_if_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(branch_if))); - let branch_else_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(branch_else))); - - let mut branch_if_variable_maps = variable_maps.clone(); - graphs.insert(branch_if_mut.try_borrow()?.id().into(), MaybeGraph::Started); - self.visit_step( - Rc::clone(&branch_if_mut), - &mut branch_if_variable_maps, - graphs, - next_steps, - do_ssa, - )?; - graphs.insert(branch_if_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); - let mut if_branch_exit = *self.exit_node().borrow(); - *self.exit_node().borrow_mut() = last_node; - - let mut branch_else_variable_maps = variable_maps.clone(); - graphs.insert( - branch_else_mut.try_borrow()?.id().into(), - MaybeGraph::Started, - ); - self.visit_step( - Rc::clone(&branch_else_mut), - &mut branch_else_variable_maps, - graphs, - next_steps, - do_ssa, - )?; - graphs.insert( - branch_else_mut.try_borrow()?.id().into(), - MaybeGraph::Inlined, - ); - let mut else_branch_exit = *self.exit_node().borrow(); - *self.exit_node().borrow_mut() = self.add_node(None); - - if do_ssa { - self.ssa_phi( - vec![ - ( - &branch_if_mut, - branch_if_variable_maps.ssa, - &mut if_branch_exit, - ), - ( - &branch_else_mut, - branch_else_variable_maps.ssa, - &mut else_branch_exit, - ), - ], - variable_maps, - &BTreeMap::new(), - )?; - } - - opcode_replacements.push(( - i, - IrOpcode::control_if_else(ControlIfElseFields { - branch_if: branch_if_mut, - branch_else: branch_else_mut, - }), - )); - - let exit_node = *self.exit_node().borrow(); - self.add_edge(if_branch_exit, exit_node, EdgeType::Forward); - self.add_edge(else_branch_exit, exit_node, EdgeType::Forward); - } - IrOpcode::control_loop(ControlLoopFields { - first_condition, - condition, - body, - flip_if, - pre_body, - }) => { - let new_var_map: BTreeMap<_, _> = step - .try_borrow()? - .globally_scoped_variables()? - .map(|global_var| (global_var, RcVar::new_empty())) - .collect(); - if do_ssa { - additional_opcodes.push(( - i, - new_var_map - .iter() - .map(|(global_var, new_var)| { - let (var_access, accessed_var) = variable_maps - .use_current_ssa_for_global::<_, _, _, HQResult<_>>( - global_var, - |ssa_var| { - Ok(( - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(ssa_var.clone()), - local_read: RefCell::new(true), - }), - ssa_var.clone(), - )) - }, - |arg_index, arg_var| { - Ok(( - IrOpcode::procedures_argument( - ProceduresArgumentFields { - index: arg_index, - arg_var: arg_var.clone(), - in_warped: true, - arg_vars: Rc::clone( - &maybe_proc_context - .as_ref() - .ok_or_else(|| { - make_hq_bug!( - "tried to access proc \ - argument when proc \ - context was None" - ) - })? - .arg_vars, - ), - }, - ), - arg_var.clone(), - )) - }, - || { - Ok(( - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(global_var.clone()), - local_read: RefCell::new(false), - }), - global_var.clone(), - )) - }, - )?; - let push_node = self.add_node(Some(StackOperation::Push( - VarTarget::Var(accessed_var), - ))); - let pop_node = self.add_node(Some(StackOperation::Pop( - VarTarget::Var(new_var.clone()), - ))); - let exit_node = *self.exit_node().borrow(); - self.add_edge(exit_node, push_node, EdgeType::Forward); - self.add_edge(push_node, pop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = pop_node; - variable_maps - .ssa - .insert(global_var.clone(), new_var.clone()); - Ok([ - var_access, - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(new_var.clone()), - local_write: RefCell::new(true), - first_write: RefCell::new(true), - }), - ]) - }) - .collect::>>()? - .into_iter() - .flatten() - .collect(), - )); - } - if let Some(first_condition_step) = first_condition { - let first_condition_mut = - Rc::new(Rc::unwrap_or_clone(Rc::clone(first_condition_step))); - graphs.insert( - first_condition_mut.try_borrow()?.id().into(), - MaybeGraph::Started, - ); - self.visit_step( - Rc::clone(&first_condition_mut), - // we don't need to keep track of this map as no variables should be set in the first condition - &mut variable_maps.clone(), - graphs, - next_steps, - do_ssa, - )?; - graphs.insert( - first_condition_mut.try_borrow()?.id().into(), - MaybeGraph::Inlined, - ); - let drop_node = self.add_node(Some(StackOperation::Drop)); // we consume the top item on the type stack as an i32.eqz - let end_node = *self.exit_node().borrow(); - self.add_edge(end_node, drop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = drop_node; - let first_cond_exit = drop_node; - - let header_node = self.add_node(None); - self.add_edge(first_cond_exit, header_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = header_node; - - let pre_body_mut = pre_body - .as_ref() - .map(|pre_body_real| -> HQResult<_> { - let pre_body_mut = - Rc::new(Rc::unwrap_or_clone(Rc::clone(pre_body_real))); - let mut pre_body_variable_maps = variable_maps.clone(); - graphs.insert( - pre_body_mut.try_borrow()?.id().into(), - MaybeGraph::Started, - ); - self.visit_step( - Rc::clone(&pre_body_mut), - &mut pre_body_variable_maps, - graphs, - next_steps, - do_ssa, - )?; - graphs.insert( - pre_body_mut.try_borrow()?.id().into(), - MaybeGraph::Inlined, - ); - Ok(pre_body_mut) - }) - .transpose()?; - - let body_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(body))); - let mut body_variable_maps = variable_maps.clone(); - graphs.insert(body_mut.try_borrow()?.id().into(), MaybeGraph::Started); - self.visit_step( - Rc::clone(&body_mut), - &mut body_variable_maps, - graphs, - next_steps, - do_ssa, - )?; - graphs.insert(body_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); - - let condition_mut: InlinedStep = - Rc::new(Rc::unwrap_or_clone(Rc::clone(condition))); - graphs.insert(condition_mut.try_borrow()?.id().into(), MaybeGraph::Started); - self.visit_step( - Rc::clone(&condition_mut), - &mut body_variable_maps, - graphs, - next_steps, - do_ssa, - )?; - graphs.insert(condition_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); - let drop_node = self.add_node(Some(StackOperation::Drop)); - let end_node = *self.exit_node().borrow(); - self.add_edge(end_node, drop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = drop_node; - let mut cond_exit = drop_node; - - if do_ssa { - self.ssa_phi( - vec![(&condition_mut, body_variable_maps.ssa, &mut cond_exit)], - variable_maps, - &new_var_map, - )?; - } - - opcode_replacements.push(( - i, - IrOpcode::control_loop(ControlLoopFields { - body: body_mut, - first_condition: Some(first_condition_mut), - flip_if: *flip_if, - condition: condition_mut, - pre_body: pre_body_mut, - }), - )); - - self.add_edge(cond_exit, header_node, EdgeType::BackLink); - *self.exit_node().borrow_mut() = cond_exit; - } else { - let header_node = self.add_node(None); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, header_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = header_node; - - let condition_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(condition))); - let mut body_variable_maps = variable_maps.clone(); - graphs.insert(condition_mut.try_borrow()?.id().into(), MaybeGraph::Started); - self.visit_step( - Rc::clone(&condition_mut), - &mut body_variable_maps, - graphs, - next_steps, - do_ssa, - )?; - graphs.insert(condition_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); - let drop_node = self.add_node(Some(StackOperation::Drop)); - let end_node = *self.exit_node().borrow(); - self.add_edge(end_node, drop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = drop_node; - - let pre_body_mut = pre_body - .as_ref() - .map(|pre_body_real| -> HQResult<_> { - let pre_body_mut = - Rc::new(Rc::unwrap_or_clone(Rc::clone(pre_body_real))); - let mut pre_body_variable_maps = variable_maps.clone(); - graphs.insert( - pre_body_mut.try_borrow()?.id().into(), - MaybeGraph::Started, - ); - self.visit_step( - Rc::clone(&pre_body_mut), - &mut pre_body_variable_maps, - graphs, - next_steps, - do_ssa, - )?; - graphs.insert( - pre_body_mut.try_borrow()?.id().into(), - MaybeGraph::Inlined, - ); - Ok(pre_body_mut) - }) - .transpose()?; - - let body_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(body))); - graphs.insert(body_mut.try_borrow()?.id().into(), MaybeGraph::Started); - self.visit_step( - Rc::clone(&body_mut), - &mut body_variable_maps, - graphs, - next_steps, - do_ssa, - )?; - graphs.insert(body_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); - let mut body_exit = *self.exit_node().borrow(); - - if do_ssa { - self.ssa_phi( - vec![(&body_mut, body_variable_maps.ssa, &mut body_exit)], - variable_maps, - &new_var_map, - )?; - } - - self.add_edge(body_exit, header_node, EdgeType::BackLink); - *self.exit_node().borrow_mut() = body_exit; - - opcode_replacements.push(( - i, - IrOpcode::control_loop(ControlLoopFields { - flip_if: *flip_if, - body: body_mut, - first_condition: None, - condition: condition_mut, - pre_body: pre_body_mut, - }), - )); - } - let loop_exit = *self.exit_node().borrow(); - let new_node = self.add_node(None); - self.add_edge(loop_exit, new_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = new_node; - } - IrOpcode::procedures_argument(ProceduresArgumentFields { arg_var, .. }) => { - // crate::log("found proc argument."); - self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( - arg_var.clone(), - )))); - } - IrOpcode::procedures_call_warp(ProceduresCallWarpFields { proc }) => { - // crate::log!("found proc call. type stack: {type_stack:?}"); - let Some(warped_specific_proc) = &*proc.warped_specific_proc() else { - hq_bug!("tried to call_warp with no warped proc") - }; - for arg_var in warped_specific_proc - .arg_vars() - .try_borrow()? - .iter() - .rev() - .cloned() - { - let pop_node = - self.add_node(Some(StackOperation::Pop(VarTarget::Var(arg_var)))); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, pop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = pop_node; - } - for ret_var in warped_specific_proc - .return_vars() - .try_borrow()? - .iter() - .cloned() - { - let push_node = - self.add_node(Some(StackOperation::Push(VarTarget::Var(ret_var)))); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, push_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = push_node; - } - } - IrOpcode::procedures_call_nonwarp(ProceduresCallNonwarpFields { - proc, - next_step, - }) => { - // crate::log!("found proc call. type stack: {type_stack:?}"); - let Some(nonwarped_specific_proc) = &*proc.nonwarped_specific_proc() else { - hq_bug!("tried to call_nonwarp with no non-warped proc") - }; - for arg_var in nonwarped_specific_proc - .arg_vars() - .try_borrow()? - .iter() - .rev() - .cloned() - { - let pop_node = - self.add_node(Some(StackOperation::Pop(VarTarget::Var(arg_var)))); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, pop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = pop_node; - } - next_steps.push(*next_step); - should_propagate_ssa = true; - break 'opcode_loop; - // crate::log!("type stack after proc call: {type_stack:?}"); - } - IrOpcode::hq_yield(HqYieldFields { mode }) => match mode { - YieldMode::Inline(step) => { - // crate::log("found inline step to visit"); - let step_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(step))); - graphs.insert(step_mut.try_borrow()?.id().into(), MaybeGraph::Started); - self.visit_step( - Rc::clone(&step_mut), - variable_maps, - graphs, - next_steps, - do_ssa, - )?; - graphs.insert(step_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); - } - YieldMode::None | YieldMode::Return => { - // crate::log("found a yield::none, breaking"); - should_propagate_ssa = true; - step_ended_on_stop = true; - break 'opcode_loop; - } - YieldMode::Schedule(_) => (), // handled at bottom of loop - }, - _ => { - let opcode_node = self.add_node(Some(StackOperation::Opcode(opcode.clone()))); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, opcode_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = opcode_node; - } - } - if let Some(next_step) = opcode.yields_to_next_step() { - next_steps.push(next_step); - should_propagate_ssa = true; - break 'opcode_loop; - } - } - - if (step.try_borrow()?.used_non_inline() || step_ended_on_stop) - && step.try_borrow()?.context().warp - && let Some(proc_context) = maybe_proc_context.as_ref() - { - // crate::log!( - // "found spare items on type stack at end of step visit, in warped proc: \ - // {type_stack:?}" - // ); - // crate::log!( - // "return vars: {}", - // proc_context.return_vars().try_borrow()?.len() - // ); - for ret_var in (*proc_context.ret_vars).borrow().iter().rev().cloned() { - let pop_node = self.add_node(Some(StackOperation::Pop(VarTarget::Var(ret_var)))); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, pop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = pop_node; - } - } - - { - let mut step_mut = step.try_borrow_mut()?; - let opcodes = step_mut.opcodes_mut(); - - for (index, opcode_replacement) in opcode_replacements { - *opcodes - .get_mut(index) - .ok_or_else(|| make_hq_bug!("opcode index out of bounds"))? = - opcode_replacement; - } - - for (index, additional_opcodes) in additional_opcodes.into_iter().rev() { - opcodes.splice(index..index, additional_opcodes); - } - } - - if should_propagate_ssa { - let post_yield = { - if let Some(last_op) = step.try_borrow()?.opcodes().last() - && matches!( - last_op, - IrOpcode::hq_yield(_) | IrOpcode::procedures_call_nonwarp(_) - ) - { - true - } else { - false - } - }; - let mut step_mut = step.try_borrow_mut()?; - let opcodes = step_mut.opcodes_mut(); - let yield_op = if post_yield { - #[expect(clippy::unwrap_used, reason = "guaranteed last element")] - Some(opcodes.pop().unwrap()) - } else { - None - }; - for (global_var, ssa_var) in &variable_maps.ssa { - let push_node = - self.add_node(Some(StackOperation::Push(VarTarget::Var(ssa_var.clone())))); - let pop_node = self.add_node(Some(StackOperation::Pop(VarTarget::Var( - global_var.clone(), - )))); - let last_node = *self.exit_node().borrow(); - self.add_edge(last_node, push_node, EdgeType::Forward); - self.add_edge(push_node, pop_node, EdgeType::Forward); - *self.exit_node().borrow_mut() = pop_node; - // crate::log("adding outer variable writes"); - opcodes.extend([ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(ssa_var.clone()), - local_read: RefCell::new(true), - }), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(global_var.clone()), - local_write: RefCell::new(false), - first_write: RefCell::new(false), - }), - ]); - } - if post_yield { - #[expect(clippy::unwrap_used, reason = "guaranteed to be Some")] - opcodes.push(yield_op.unwrap()); - } - } - Ok(()) - } - - /// Insert an SSA "phi function"; that is, propagate any new SSAs to the outer scope, - /// at a so-called dominance frontier (where different paths meet) - fn ssa_phi( - &self, - mut ssa_blocks: Vec<(&InlinedStep, BTreeMap, &mut NodeIndex)>, - variable_maps: &mut VariableMaps, - ssa_write_map: &BTreeMap, - ) -> HQResult<()> { - let mut lower_ssas: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new(); - let write_map_was_empty = ssa_write_map.is_empty(); - - for (block, block_ssa, _) in &ssa_blocks { - for (global, block_local) in block_ssa { - lower_ssas - .entry(global.clone()) - .and_modify(|ssa_block_map| { - ssa_block_map.insert(Rc::clone(block), block_local.clone()); - }) - .or_insert_with(|| BTreeMap::from([(Rc::clone(block), block_local.clone())])); - } - } - - let mut block_exits: BTreeMap<_, _> = ssa_blocks - .iter_mut() - .map(|(block, _block_ssas, block_exit)| (Rc::clone(block), block_exit)) - .collect(); - - let mut block_var_writes = BTreeMap::new(); - - for (global, block_ssas) in lower_ssas { - let new_var = ssa_write_map - .get(&global) - .cloned() - .unwrap_or_else(RcVar::new_empty); - for block in block_exits.keys() { - if !block_ssas.contains_key(block) { - block_var_writes - .entry(Rc::clone(block)) - .or_insert_with(Vec::new) - .push(( - variable_maps - .ssa - .get(&global) - .cloned() - .map_or_else(|| (global.clone(), false), |var| (var, true)), - new_var.clone(), - )); - } - } - for (block, block_ssa) in &block_ssas { - block_var_writes - .entry(Rc::clone(block)) - .or_insert_with(Vec::new) - .push(((block_ssa.clone(), true), new_var.clone())); - } - variable_maps.ssa.insert(global.clone(), new_var.clone()); - } - - for (block, var_writes) in &block_var_writes { - let Some(block_exit) = block_exits.get_mut(block) else { - hq_bug!("couldn't find SSA block exit") - }; - let mut block_mut = block.try_borrow_mut()?; - let opcodes = block_mut.opcodes_mut(); - if !matches!( - opcodes.last(), - Some(IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Return, - })) - ) { - opcodes.reserve_exact(var_writes.len() * 2); - for ((var_read, read_local), var_write) in var_writes { - let push_node = - self.add_node(Some(StackOperation::Push(VarTarget::Var(var_read.clone())))); - let pop_node = - self.add_node(Some(StackOperation::Pop(VarTarget::Var(var_write.clone())))); - self.add_edge(***block_exit, push_node, EdgeType::Forward); - self.add_edge(push_node, pop_node, EdgeType::Forward); - ***block_exit = pop_node; - - opcodes.append(&mut vec![ - IrOpcode::data_variable(DataVariableFields { - var: RefCell::new(var_read.clone()), - local_read: RefCell::new(*read_local), - }), - IrOpcode::data_setvariableto(DataSetvariabletoFields { - var: RefCell::new(var_write.clone()), - local_write: RefCell::new(true), - // TODO: might this actually be the first write? - first_write: RefCell::new(write_map_was_empty), - }), - ]); - } - } - } - - Ok(()) - } -} - fn visit_step_recursively( step_index: StepIndex, project: &Rc, @@ -1116,7 +139,6 @@ fn visit_step_recursively( next_step.try_borrow()?.id().into(), MaybeGraph::Finished(graph), ); - // crate::log!("finished graph for step {}", next_step.id()); } } Ok(()) @@ -1128,11 +150,9 @@ fn visit_procedure( project: &Rc, do_ssa: bool, ) -> HQResult<()> { - // crate::log("visiting procedure"); if let Some(warped_specific_proc) = &*proc.warped_specific_proc() && let PartialStep::Finished(step_index) = warped_specific_proc.first_step()?.clone() { - // crate::log!("visiting procedure's step: {}", step.id()); let steps = project.steps().try_borrow()?; let step = steps .get(step_index.0) @@ -1163,7 +183,6 @@ fn visit_procedure( if let Some(nonwarped_specific_proc) = &*proc.nonwarped_specific_proc() && let PartialStep::Finished(step_index) = nonwarped_specific_proc.first_step()?.clone() { - // crate::log!("visiting procedure's step: {}", step.id()); visit_step_recursively(step_index, project, graphs, &BTreeMap::new(), do_ssa)?; } Ok(()) @@ -1176,7 +195,6 @@ fn split_variables_and_make_graphs( project: &Rc, do_ssa: bool, ) -> HQResult, MaybeGraph>> { - // crate::log("splitting variables and making graphs"); let mut graphs = BTreeMap::new(); for (_, target) in project.targets().borrow().iter() { for (_, proc) in target.procedures()?.iter() { @@ -1185,335 +203,11 @@ fn split_variables_and_make_graphs( } for thread in project.threads().try_borrow()?.iter() { let step_index = thread.first_step(); - // crate::log!("visiting (recursively) step from thread: {}", step.id()); visit_step_recursively(step_index, project, &mut graphs, &BTreeMap::new(), do_ssa)?; } - // crate::log("finished splitting variables and making graphs"); Ok(graphs) } -fn evaluate_type_stack( - type_stack: &Rc, - stack_op: &StackOperation, - changed_vars: &mut BTreeSet, - type_convergence: VarTypeConvergence, -) -> HQResult> { - // crate::log!("type stack: {type_stack:?}"); - Ok(match stack_op { - StackOperation::Opcode(op) => { - let mut local_stack = Rc::clone(type_stack); - let inputs_len = op.acceptable_inputs()?.len(); - // crate::log!("opcode: {op}"); - // crate::log!("stack length: {}, inputs len: {}", stack.len(), inputs_len); - // crate::log!("stack: {:?}", stack); - let inputs: Rc<[_]> = local_stack - .by_ref() - .take(inputs_len) - .map(|ty: IrType| if ty.is_none() { IrType::Any } else { ty }) - .collect::>() - .into_iter() - .rev() - .collect(); - hq_assert!( - inputs.len() == inputs_len, - "can't pop {} elements from stack of length {}", - inputs_len, - inputs.len() - ); - let output = op.output_type(inputs)?; - match output { - ReturnType::Singleton(ty) => { - local_stack = Rc::new(TypeStack::Cons(ty, Rc::clone(&local_stack))); - } - ReturnType::MultiValue(tys) => { - for ty in tys.iter().copied() { - local_stack = Rc::new(TypeStack::Cons(ty, local_stack)); - } - } - ReturnType::None => (), - } - local_stack - } - StackOperation::Drop => { - let TypeStack::Cons(_, tail) = &**type_stack else { - hq_bug!("can't drop from empty stack"); - }; - Rc::clone(tail) - } - StackOperation::Push(var_target) => Rc::new(TypeStack::Cons( - *var_target.possible_types(), - Rc::clone(type_stack), - )), - StackOperation::Pop(target) => { - let TypeStack::Cons(head, tail) = &**type_stack else { - hq_bug!("can't pop from empty stack"); - }; - let expanded_type = match type_convergence { - VarTypeConvergence::Any => IrType::Any, - VarTypeConvergence::Base => head.base_types().fold(IrType::none(), IrType::or), - VarTypeConvergence::Tight => *head, - }; - if !target.possible_types().contains(expanded_type) { - changed_vars.insert(target.clone()); - } - target.add_type(expanded_type); - Rc::clone(tail) - } - }) -} - -#[derive(Debug, Clone)] -enum TypeStack { - Nil, - Cons(IrType, Rc), -} - -impl Iterator for Rc { - type Item = IrType; - - fn next(&mut self) -> Option { - if let TypeStack::Cons(ty, tail) = &*self.clone() { - *self = Self::clone(tail); - Some(*ty) - } else { - None - } - } -} - -#[derive(Clone, Debug)] -struct DFSQueueItem { - pub edge: EdgeIndex, - pub type_stack: Rc, -} - -fn visit_graph( - graph: &VarGraph, - changed_vars: &mut BTreeSet, - type_convergence: VarTypeConvergence, -) -> HQResult<()> { - let inner_graph = graph.graph().try_borrow()?; - let mut dfs_queue = vec![DFSQueueItem { - edge: EdgeIndex::new(0), // this assumes that the first node is empty and has a single forward edge. TODO: is this the case? - type_stack: Rc::new(TypeStack::Nil), - }]; - let mut joining_edges: BTreeMap>> = BTreeMap::default(); - while let Some(DFSQueueItem { edge, type_stack }) = dfs_queue.pop() { - let should_wait_for_all_incoming_edges = - if let Entry::Occupied(mut waiting_to_join) = joining_edges.entry(edge) { - let waiting = *waiting_to_join.get().try_borrow()? > 1; - *waiting_to_join.get_mut().try_borrow_mut()? -= 1; - waiting_to_join.remove(); - if waiting { - // we're still waiting for some incoming edges to be visited before we move on - continue; - } - // we've already previously waited for incoming edges, and we've now seen them all - false - } else { - true - }; - let node = inner_graph - .edge_endpoints(edge) - .ok_or_else(|| make_hq_bug!("couldn't find edge in graph"))? - .1; - let new_stack = if let Some(Some(stack_op)) = inner_graph.node_weight(node) { - evaluate_type_stack(&type_stack, stack_op, changed_vars, type_convergence)? - } else { - Rc::clone(&type_stack) - }; - // crate::log!("edge: {edge:?}, node: {node:?}"); - let can_continue = if should_wait_for_all_incoming_edges { - let joining_this = Rc::new(RefCell::new(0)); - for in_edge in inner_graph - .edges_directed(node, EdgeIn) - .filter(|e| e.id() != edge && e.weight() == &EdgeType::Forward) - { - *joining_this.try_borrow_mut()? += 1; - hq_assert!( - !joining_edges.contains_key(&in_edge.id()), - "tried to add duplicate edge {:?} to joining_edges", - in_edge - ); - joining_edges.insert(in_edge.id(), Rc::clone(&joining_this)); - } - *joining_this.try_borrow()? == 0 - } else { - true - }; - if can_continue { - for for_edge in inner_graph - .edges_directed(node, EdgeOut) - .filter(|e| e.weight() == &EdgeType::Forward) - { - dfs_queue.push(DFSQueueItem { - edge: for_edge.id(), - type_stack: Rc::clone(&new_stack), - }); - } - } - // crate::log!("dfs queue: {dfs_queue:?}"); - // crate::log!("joining_edges: {joining_edges:?}"); - } - // crate::log("ok."); - Ok(()) -} - -fn iterate_graphs<'a, I>(graphs: &'a I, type_convergence: VarTypeConvergence) -> HQResult<()> -where - I: Iterator)> + Clone, -{ - loop { - let mut changed_vars: BTreeSet = BTreeSet::new(); - for graph in graphs.clone() { - crate::log!("visiting graph for step {}", graph.1); - // use petgraph::dot::Dot; - // crate::log!( - // "{:?}exit node: {:?}", - // Dot::with_config( - // &*graph.graph().borrow(), - // &[ - // //DotConfig::NodeIndexLabel - // ] - // ), - // *graph.exit_node().borrow() - // ); - visit_graph(graph.0, &mut changed_vars, type_convergence)?; - } - // this must eventually converge, because the sequence of types for each variable at each - // iteration is increasing, but the set of types is finite - if changed_vars.is_empty() { - break; - } - } - Ok(()) -} - -fn box_proc_returns( - step: S, - rev_ret_vars: &[&RcVar], - visited_steps: &mut BTreeSet>, -) -> HQResult<()> -where - S: Deref>, -{ - if visited_steps.contains(step.try_borrow()?.id()) { - return Ok(()); - } - - visited_steps.insert(step.try_borrow()?.id().into()); - - let opcodes_len = step.try_borrow()?.opcodes().len(); - - for i in 0..opcodes_len { - let inlined_steps = step - .try_borrow()? - .opcodes() - .get(i) - .ok_or_else(|| make_hq_bug!("opcode index out of bounds"))? - .inline_steps(true); - if let Some(inline_steps) = inlined_steps { - let new_inline_steps = inline_steps - .iter() - .map(|inline_step| { - let inline_step_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(inline_step))); - box_proc_returns(Rc::clone(&inline_step_mut), rev_ret_vars, visited_steps)?; - Ok(inline_step_mut) - }) - .collect::>>()?; - for (step_ref_mut, new_inline_step) in step - .try_borrow_mut()? - .opcodes_mut() - .get_mut(i) - .ok_or_else(|| make_hq_bug!("opcode index out of bounds"))? - .inline_steps_mut(true) - .ok_or_else(|| { - make_hq_bug!("inline_steps_mut was None when inline_steps was Some") - })? - .iter_mut() - .zip(new_inline_steps) - { - **step_ref_mut = new_inline_step; - } - } - } - - let mut box_additions = Vec::new(); - - let mut rev_vars_iter = rev_ret_vars.iter(); - - let mut to_skip = 0; - - for (i, opcode) in step - .try_borrow()? - .opcodes() - .iter() - .enumerate() - .rev() - .filter(|(_, op)| { - !matches!( - op, - IrOpcode::hq_yield(HqYieldFields { - mode: YieldMode::Return, - }), - ) - }) - { - #[expect( - clippy::wildcard_enum_match_arm, - reason = "too many variants to match explicitly" - )] - match opcode { - IrOpcode::data_variable(DataVariableFields { var, .. }) => { - if to_skip > 0 { - to_skip -= 1; - continue; - } - let Some(ret_var) = rev_vars_iter.next() else { - break; - }; - if var.borrow().possible_types().is_base_type() - && !ret_var.possible_types().is_base_type() - { - box_additions.push((i + 1, *ret_var.possible_types())); - } - } - IrOpcode::procedures_argument(ProceduresArgumentFields { arg_var, .. }) => { - if to_skip > 0 { - to_skip -= 1; - continue; - } - let Some(ret_var) = rev_vars_iter.next() else { - break; - }; - if arg_var.possible_types().is_base_type() - && !ret_var.possible_types().is_base_type() - { - box_additions.push((i + 1, *ret_var.possible_types())); - } - } - IrOpcode::data_setvariableto(_) => { - to_skip += 1; - } - _ => { - break; - } - } - } - - let mut step_mut = step.try_borrow_mut()?; - let opcodes_mut = step_mut.opcodes_mut(); - - for (box_addition, output_ty) in box_additions { - opcodes_mut.splice( - box_addition..box_addition, - vec![IrOpcode::hq_box(HqBoxFields { output_ty })], - ); - } - - Ok(()) -} - /// A token type that cannot be instantiated from anywhere else (since the field is private) /// - used as proof that we've carried out these optimisations. #[derive(Copy, Clone)] @@ -1524,7 +218,6 @@ pub fn optimise_variables( type_convergence: VarTypeConvergence, do_ssa: Switch, ) -> HQResult { - // crate::log("carrying out variable optimisation"); let induced_do_ssa = do_ssa == Switch::On && type_convergence != VarTypeConvergence::Any; let maybe_graphs = split_variables_and_make_graphs(project, induced_do_ssa)?; let graphs = maybe_graphs @@ -1538,19 +231,12 @@ pub fn optimise_variables( }) .filter_map_ok(identity) .collect::>>()?; - // crate::log!("graphs num: {}", graphs.len()); iterate_graphs( &graphs.iter().map(|(s, g)| (*g, (*s).clone())), type_convergence, )?; - crate::log("finished iterating graphs"); for step in project.steps().try_borrow()?.iter() { - crate::log!("inserting casts for step {}", step.try_borrow()?.id()); insert_casts(step.try_borrow_mut()?.opcodes_mut(), false, true)?; - crate::log!( - "finished inserting casts for step {}", - step.try_borrow()?.id() - ); } // we might have made some procedure return types boxed, so we now need to go through and box the diff --git a/src/optimisation/ssa/box_proc_returns.rs b/src/optimisation/ssa/box_proc_returns.rs new file mode 100644 index 00000000..fb67138d --- /dev/null +++ b/src/optimisation/ssa/box_proc_returns.rs @@ -0,0 +1,132 @@ +use core::ops::Deref; + +use crate::instructions::{ + DataVariableFields, HqBoxFields, HqYieldFields, IrOpcode, ProceduresArgumentFields, YieldMode, +}; +use crate::ir::{RcVar, Step}; +use crate::prelude::*; + +pub fn box_proc_returns( + step: S, + rev_ret_vars: &[&RcVar], + visited_steps: &mut BTreeSet>, +) -> HQResult<()> +where + S: Deref>, +{ + if visited_steps.contains(step.try_borrow()?.id()) { + return Ok(()); + } + + visited_steps.insert(step.try_borrow()?.id().into()); + + let opcodes_len = step.try_borrow()?.opcodes().len(); + + for i in 0..opcodes_len { + let inlined_steps = step + .try_borrow()? + .opcodes() + .get(i) + .ok_or_else(|| make_hq_bug!("opcode index out of bounds"))? + .inline_steps(true); + if let Some(inline_steps) = inlined_steps { + let new_inline_steps = inline_steps + .iter() + .map(|inline_step| { + let inline_step_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(inline_step))); + box_proc_returns(Rc::clone(&inline_step_mut), rev_ret_vars, visited_steps)?; + Ok(inline_step_mut) + }) + .collect::>>()?; + for (step_ref_mut, new_inline_step) in step + .try_borrow_mut()? + .opcodes_mut() + .get_mut(i) + .ok_or_else(|| make_hq_bug!("opcode index out of bounds"))? + .inline_steps_mut(true) + .ok_or_else(|| { + make_hq_bug!("inline_steps_mut was None when inline_steps was Some") + })? + .iter_mut() + .zip(new_inline_steps) + { + **step_ref_mut = new_inline_step; + } + } + } + + let mut box_additions = Vec::new(); + + let mut rev_vars_iter = rev_ret_vars.iter(); + + let mut to_skip = 0; + + for (i, opcode) in step + .try_borrow()? + .opcodes() + .iter() + .enumerate() + .rev() + .filter(|(_, op)| { + !matches!( + op, + IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Return, + }), + ) + }) + { + #[expect( + clippy::wildcard_enum_match_arm, + reason = "too many variants to match explicitly" + )] + match opcode { + IrOpcode::data_variable(DataVariableFields { var, .. }) => { + if to_skip > 0 { + to_skip -= 1; + continue; + } + let Some(ret_var) = rev_vars_iter.next() else { + break; + }; + if var.borrow().possible_types().is_base_type() + && !ret_var.possible_types().is_base_type() + { + box_additions.push((i + 1, *ret_var.possible_types())); + } + } + IrOpcode::procedures_argument(ProceduresArgumentFields { arg_var, .. }) => { + if to_skip > 0 { + to_skip -= 1; + continue; + } + let Some(ret_var) = rev_vars_iter.next() else { + break; + }; + if arg_var.possible_types().is_base_type() + && !ret_var.possible_types().is_base_type() + { + box_additions.push((i + 1, *ret_var.possible_types())); + } + } + IrOpcode::data_setvariableto(_) => { + to_skip += 1; + } + _ => { + break; + } + } + } + + let mut step_mut = step.try_borrow_mut()?; + let opcodes_mut = step_mut.opcodes_mut(); + + for (box_addition, output_ty) in box_additions { + opcodes_mut.splice( + box_addition..box_addition, + vec![IrOpcode::hq_box(HqBoxFields { output_ty })], + ); + } + + Ok(()) +} diff --git a/src/optimisation/ssa/type_convergence.rs b/src/optimisation/ssa/type_convergence.rs new file mode 100644 index 00000000..a6cd9a7a --- /dev/null +++ b/src/optimisation/ssa/type_convergence.rs @@ -0,0 +1,187 @@ +use alloc::collections::btree_map::Entry; + +use petgraph::graph::EdgeIndex; +use petgraph::visit::EdgeRef; +use petgraph::{Incoming as EdgeIn, Outgoing as EdgeOut}; + +use super::var_graph::{EdgeType, StackOperation, VarGraph, VarTarget}; +use crate::ir::{ReturnType, Type as IrType}; +use crate::prelude::*; +use crate::wasm::flags::VarTypeConvergence; + +fn evaluate_type_stack( + type_stack: &Rc, + stack_op: &StackOperation, + changed_vars: &mut BTreeSet, + type_convergence: VarTypeConvergence, +) -> HQResult> { + Ok(match stack_op { + StackOperation::Opcode(op) => { + let mut local_stack = Rc::clone(type_stack); + let inputs_len = op.acceptable_inputs()?.len(); + let inputs: Rc<[_]> = local_stack + .by_ref() + .take(inputs_len) + .map(|ty: IrType| if ty.is_none() { IrType::Any } else { ty }) + .collect::>() + .into_iter() + .rev() + .collect(); + hq_assert!( + inputs.len() == inputs_len, + "can't pop {} elements from stack of length {}", + inputs_len, + inputs.len() + ); + let output = op.output_type(inputs)?; + match output { + ReturnType::Singleton(ty) => { + local_stack = Rc::new(TypeStack::Cons(ty, Rc::clone(&local_stack))); + } + ReturnType::MultiValue(tys) => { + for ty in tys.iter().copied() { + local_stack = Rc::new(TypeStack::Cons(ty, local_stack)); + } + } + ReturnType::None => (), + } + local_stack + } + StackOperation::Drop => { + let TypeStack::Cons(_, tail) = &**type_stack else { + hq_bug!("can't drop from empty stack"); + }; + Rc::clone(tail) + } + StackOperation::Push(var_target) => Rc::new(TypeStack::Cons( + *var_target.possible_types(), + Rc::clone(type_stack), + )), + StackOperation::Pop(target) => { + let TypeStack::Cons(head, tail) = &**type_stack else { + hq_bug!("can't pop from empty stack"); + }; + let expanded_type = match type_convergence { + VarTypeConvergence::Any => IrType::Any, + VarTypeConvergence::Base => head.base_types().fold(IrType::none(), IrType::or), + VarTypeConvergence::Tight => *head, + }; + if !target.possible_types().contains(expanded_type) { + changed_vars.insert(target.clone()); + } + target.add_type(expanded_type); + Rc::clone(tail) + } + }) +} + +#[derive(Debug, Clone)] +enum TypeStack { + Nil, + Cons(IrType, Rc), +} + +impl Iterator for Rc { + type Item = IrType; + + fn next(&mut self) -> Option { + if let TypeStack::Cons(ty, tail) = &*self.clone() { + *self = Self::clone(tail); + Some(*ty) + } else { + None + } + } +} + +#[derive(Clone, Debug)] +struct DFSQueueItem { + pub edge: EdgeIndex, + pub type_stack: Rc, +} + +fn visit_graph( + graph: &VarGraph, + changed_vars: &mut BTreeSet, + type_convergence: VarTypeConvergence, +) -> HQResult<()> { + let inner_graph = graph.graph().try_borrow()?; + let mut dfs_queue = vec![DFSQueueItem { + edge: EdgeIndex::new(0), // this assumes that the first node is empty and has a single forward edge. TODO: is this the case? + type_stack: Rc::new(TypeStack::Nil), + }]; + let mut joining_edges: BTreeMap>> = BTreeMap::default(); + while let Some(DFSQueueItem { edge, type_stack }) = dfs_queue.pop() { + let should_wait_for_all_incoming_edges = + if let Entry::Occupied(mut waiting_to_join) = joining_edges.entry(edge) { + let waiting = *waiting_to_join.get().try_borrow()? > 1; + *waiting_to_join.get_mut().try_borrow_mut()? -= 1; + waiting_to_join.remove(); + if waiting { + // we're still waiting for some incoming edges to be visited before we move on + continue; + } + // we've already previously waited for incoming edges, and we've now seen them all + false + } else { + true + }; + let node = inner_graph + .edge_endpoints(edge) + .ok_or_else(|| make_hq_bug!("couldn't find edge in graph"))? + .1; + let new_stack = if let Some(Some(stack_op)) = inner_graph.node_weight(node) { + evaluate_type_stack(&type_stack, stack_op, changed_vars, type_convergence)? + } else { + Rc::clone(&type_stack) + }; + let can_continue = if should_wait_for_all_incoming_edges { + let joining_this = Rc::new(RefCell::new(0)); + for in_edge in inner_graph + .edges_directed(node, EdgeIn) + .filter(|e| e.id() != edge && e.weight() == &EdgeType::Forward) + { + *joining_this.try_borrow_mut()? += 1; + hq_assert!( + !joining_edges.contains_key(&in_edge.id()), + "tried to add duplicate edge {:?} to joining_edges", + in_edge + ); + joining_edges.insert(in_edge.id(), Rc::clone(&joining_this)); + } + *joining_this.try_borrow()? == 0 + } else { + true + }; + if can_continue { + for for_edge in inner_graph + .edges_directed(node, EdgeOut) + .filter(|e| e.weight() == &EdgeType::Forward) + { + dfs_queue.push(DFSQueueItem { + edge: for_edge.id(), + type_stack: Rc::clone(&new_stack), + }); + } + } + } + Ok(()) +} + +pub fn iterate_graphs<'a, I>(graphs: &'a I, type_convergence: VarTypeConvergence) -> HQResult<()> +where + I: Iterator)> + Clone, +{ + loop { + let mut changed_vars: BTreeSet = BTreeSet::new(); + for graph in graphs.clone() { + visit_graph(graph.0, &mut changed_vars, type_convergence)?; + } + // this must eventually converge, because the sequence of types for each variable at each + // iteration is increasing, but the set of types is finite + if changed_vars.is_empty() { + break; + } + } + Ok(()) +} diff --git a/src/optimisation/ssa/var_graph.rs b/src/optimisation/ssa/var_graph.rs new file mode 100644 index 00000000..5f987ef1 --- /dev/null +++ b/src/optimisation/ssa/var_graph.rs @@ -0,0 +1,977 @@ +use core::hash::{Hash, Hasher}; +use core::ops::Deref; + +use petgraph::graph::{EdgeIndex, NodeIndex}; +use petgraph::stable_graph::StableDiGraph; + +use crate::instructions::{ + ControlIfElseFields, ControlLoopFields, DataAddtolistFields, DataInsertatlistFields, + DataItemoflistFields, DataReplaceitemoflistFields, DataSetvariabletoFields, + DataTeevariableFields, DataVariableFields, HqYieldFields, IrOpcode, ProceduresArgumentFields, + ProceduresCallNonwarpFields, ProceduresCallWarpFields, YieldMode, +}; +use crate::ir::{InlinedStep, ProcContext, RcList, RcVar, Step, StepIndex, Type as IrType}; +use crate::prelude::*; + +#[derive(Clone, Debug)] +pub enum StackOperation { + Drop, + Push(VarTarget), + Pop(VarTarget), + /// this shouldn't be anything that branches or mutates variables in any way, + /// i.e. not loop, if/else, yield, set variable, etc + Opcode(IrOpcode), +} + +#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub enum VarTarget { + Var(RcVar), + List(RcList), +} + +impl VarTarget { + pub fn possible_types(&self) -> core::cell::Ref<'_, IrType> { + match self { + Self::Var(var) => var.possible_types(), + Self::List(list) => list.possible_types(), + } + } + + pub fn add_type(&self, ty: IrType) { + match self { + Self::Var(var) => var.add_type(ty), + Self::List(list) => list.add_type(ty), + } + } +} + +pub enum MaybeGraph { + Started, + Inlined, + Finished(VarGraph), +} + +#[derive(Clone)] +pub struct VariableMaps<'a> { + /// global variable -> local variable + pub ssa: BTreeMap, + /// global variable -> (arg index, arg variable) + pub proc_args: &'a BTreeMap, +} + +impl<'a> VariableMaps<'a> { + pub const fn new_with_proc_args(proc_args: &'a BTreeMap) -> Self { + Self { + ssa: BTreeMap::new(), + proc_args, + } + } + + fn use_current_ssa_for_global( + &self, + global_var: &RcVar, + if_ssa: S, + if_arg: A, + if_global: G, + ) -> T + where + S: FnOnce(&RcVar) -> T, + A: FnOnce(usize, &RcVar) -> T, + G: FnOnce() -> T, + { + self.ssa.get(global_var).map_or_else( + || { + if let Some((arg_index, arg_var)) = self.proc_args.get(global_var) { + if_arg(*arg_index, arg_var) + } else { + if_global() + } + }, + if_ssa, + ) + } +} + +/// Is this edge a backlink or not? +/// +/// We could calculate this dynamically during the reduction stage but seeing as we can know this +/// when constructing the graph, we might as well do it now. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum EdgeType { + Forward, + BackLink, +} + +pub type NodeWeight = Option; + +#[derive(Debug)] +pub struct BaseVarGraph { + pub graph: RefCell>, + pub exit_node: RefCell, +} + +#[derive(Clone, Debug)] +pub struct VarGraph(Rc); + +impl Hash for VarGraph { + fn hash(&self, state: &mut H) { + Rc::as_ptr(&self.0).hash(state); + } +} + +impl VarGraph { + pub fn new() -> Self { + let mut graph = StableDiGraph::new(); + let first_node = graph.add_node(None); + let exit_node = graph.add_node(None); + graph.add_edge(first_node, exit_node, EdgeType::Forward); + Self(Rc::new(BaseVarGraph { + graph: RefCell::new(graph), + exit_node: RefCell::new(exit_node), + })) + } + + pub fn graph(&self) -> &RefCell> { + &self.0.graph + } + + fn exit_node(&self) -> &RefCell { + &self.0.exit_node + } + + fn add_node(&self, weight: NodeWeight) -> NodeIndex { + self.graph().borrow_mut().add_node(weight) + } + + fn add_node_at_end(&self, weight: NodeWeight) { + let new_node = self.add_node(weight); + let end_node = *self.exit_node().borrow(); + self.add_edge(end_node, new_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = new_node; + } + + fn add_edge(&self, a: NodeIndex, b: NodeIndex, weight: EdgeType) -> EdgeIndex { + self.graph().borrow_mut().add_edge(a, b, weight) + } + + /// Visits a step to split its variables and construct a variable graph for it. + pub fn visit_step( + &mut self, + step: S, + variable_maps: &mut VariableMaps<'_>, + graphs: &mut BTreeMap, MaybeGraph>, + next_steps: &mut Vec, + do_ssa: bool, + ) -> HQResult<()> + where + S: Deref>, + { + if let Some(MaybeGraph::Inlined | MaybeGraph::Finished(_)) = + graphs.get(step.try_borrow()?.id()) + { + // we've already visited this step. + return Ok(()); + } + + let maybe_proc_context = { + let step_tmp = step.try_borrow()?; + step_tmp.context().proc_context.clone() + }; + + let mut should_propagate_ssa = + step.try_borrow()?.used_non_inline() && maybe_proc_context.is_none(); + let mut step_ended_on_stop = false; + + let mut opcode_replacements: Vec<(usize, IrOpcode)> = vec![]; + let mut additional_opcodes: Vec<(usize, Vec)> = vec![]; + 'opcode_loop: for (i, opcode) in step.try_borrow()?.opcodes().iter().enumerate() { + // let's just assume that input types match up. + #[expect(clippy::wildcard_enum_match_arm, reason = "too many variants to match")] + match opcode { + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var, + local_write: locality, + first_write, + }) => { + let already_local = *locality.try_borrow()?; + if already_local { + if var.try_borrow()?.possible_types().is_none() { + let pop_node = self.add_node(Some(StackOperation::Pop( + VarTarget::Var(var.try_borrow()?.clone()), + ))); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, pop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = pop_node; + } else { + let drop_node = self.add_node(Some(StackOperation::Drop)); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, drop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = drop_node; + } + continue 'opcode_loop; + } + if !do_ssa { + let pop_node = self.add_node(Some(StackOperation::Pop(VarTarget::Var( + var.try_borrow()?.clone(), + )))); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, pop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = pop_node; + continue 'opcode_loop; + } + let new_variable = RcVar::new_empty(); + { + variable_maps + .ssa + .insert(var.try_borrow()?.clone(), new_variable.clone()); + } + *var.try_borrow_mut()? = new_variable; + *first_write.try_borrow_mut()? = true; + *locality.try_borrow_mut()? = true; + let pop_node = self.add_node(Some(StackOperation::Pop(VarTarget::Var( + var.try_borrow()?.clone(), + )))); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, pop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = pop_node; + } + IrOpcode::data_teevariable(DataTeevariableFields { + var, + local_read_write: locality, + }) => { + let already_local = *locality.try_borrow()?; + if !do_ssa || already_local { + self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( + var.try_borrow()?.clone(), + )))); + continue 'opcode_loop; + } + let new_variable = RcVar::new_empty(); + { + variable_maps + .ssa + .insert(var.try_borrow()?.clone(), new_variable.clone()); + } + *var.try_borrow_mut()? = new_variable; + *locality.try_borrow_mut()? = true; + self.add_node_at_end(Some(StackOperation::Pop(VarTarget::Var( + var.try_borrow()?.clone(), + )))); + self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( + var.try_borrow()?.clone(), + )))); + } + IrOpcode::data_variable(DataVariableFields { var, local_read }) => { + if *local_read.try_borrow()? { + self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( + var.try_borrow()?.clone(), + )))); + continue; + } + let gvar = &var.try_borrow()?.clone(); + let mut var_mut = var.try_borrow_mut()?; + self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( + variable_maps.use_current_ssa_for_global::<_, _, _, HQResult<_>>( + gvar, + |current_ssa_var| { + *var_mut = current_ssa_var.clone(); + *local_read.try_borrow_mut()? = true; + Ok(current_ssa_var.clone()) + }, + |var_index, arg_var| { + opcode_replacements.push(( + i, + IrOpcode::procedures_argument(ProceduresArgumentFields { + index: var_index, + arg_var: arg_var.clone(), + in_warped: true, + arg_vars: Rc::clone( + &maybe_proc_context + .as_ref() + .ok_or_else(|| { + make_hq_bug!( + "tried to access proc argument when proc \ + context was None" + ) + })? + .arg_vars, + ), + }), + )); + Ok(arg_var.clone()) + }, + || Ok(gvar.clone()), + )?, + )))); + } + IrOpcode::data_itemoflist(DataItemoflistFields { list }) => { + self.add_node_at_end(Some(StackOperation::Drop)); + self.add_node_at_end(Some(StackOperation::Push(VarTarget::List(list.clone())))); + } + IrOpcode::data_addtolist(DataAddtolistFields { list }) => { + self.add_node_at_end(Some(StackOperation::Pop(VarTarget::List(list.clone())))); + } + IrOpcode::data_replaceitemoflist(DataReplaceitemoflistFields { list }) + | IrOpcode::data_insertatlist(DataInsertatlistFields { list }) => { + self.add_node_at_end(Some(StackOperation::Pop(VarTarget::List(list.clone())))); + self.add_node_at_end(Some(StackOperation::Drop)); + } + IrOpcode::control_if_else(ControlIfElseFields { + branch_if, + branch_else, + }) => { + // the top item on the stack should be consumed by control_if_else + self.add_node_at_end(Some(StackOperation::Drop)); + let last_node = *self.exit_node().borrow(); + + let branch_if_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(branch_if))); + let branch_else_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(branch_else))); + + let mut branch_if_variable_maps = variable_maps.clone(); + graphs.insert(branch_if_mut.try_borrow()?.id().into(), MaybeGraph::Started); + self.visit_step( + Rc::clone(&branch_if_mut), + &mut branch_if_variable_maps, + graphs, + next_steps, + do_ssa, + )?; + graphs.insert(branch_if_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); + let mut if_branch_exit = *self.exit_node().borrow(); + *self.exit_node().borrow_mut() = last_node; + + let mut branch_else_variable_maps = variable_maps.clone(); + graphs.insert( + branch_else_mut.try_borrow()?.id().into(), + MaybeGraph::Started, + ); + self.visit_step( + Rc::clone(&branch_else_mut), + &mut branch_else_variable_maps, + graphs, + next_steps, + do_ssa, + )?; + graphs.insert( + branch_else_mut.try_borrow()?.id().into(), + MaybeGraph::Inlined, + ); + let mut else_branch_exit = *self.exit_node().borrow(); + *self.exit_node().borrow_mut() = self.add_node(None); + + if do_ssa { + self.ssa_phi( + vec![ + ( + &branch_if_mut, + branch_if_variable_maps.ssa, + &mut if_branch_exit, + ), + ( + &branch_else_mut, + branch_else_variable_maps.ssa, + &mut else_branch_exit, + ), + ], + variable_maps, + &BTreeMap::new(), + )?; + } + + opcode_replacements.push(( + i, + IrOpcode::control_if_else(ControlIfElseFields { + branch_if: branch_if_mut, + branch_else: branch_else_mut, + }), + )); + + let exit_node = *self.exit_node().borrow(); + self.add_edge(if_branch_exit, exit_node, EdgeType::Forward); + self.add_edge(else_branch_exit, exit_node, EdgeType::Forward); + } + IrOpcode::control_loop(fields) => { + self.loop_ssa( + &step, + variable_maps, + graphs, + next_steps, + &mut opcode_replacements, + &mut additional_opcodes, + maybe_proc_context.as_ref(), + i, + fields, + do_ssa, + )?; + } + IrOpcode::procedures_argument(ProceduresArgumentFields { arg_var, .. }) => { + self.add_node_at_end(Some(StackOperation::Push(VarTarget::Var( + arg_var.clone(), + )))); + } + IrOpcode::procedures_call_warp(ProceduresCallWarpFields { proc }) => { + let Some(warped_specific_proc) = &*proc.warped_specific_proc() else { + hq_bug!("tried to call_warp with no warped proc") + }; + for arg_var in warped_specific_proc + .arg_vars() + .try_borrow()? + .iter() + .rev() + .cloned() + { + let pop_node = + self.add_node(Some(StackOperation::Pop(VarTarget::Var(arg_var)))); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, pop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = pop_node; + } + for ret_var in warped_specific_proc + .return_vars() + .try_borrow()? + .iter() + .cloned() + { + let push_node = + self.add_node(Some(StackOperation::Push(VarTarget::Var(ret_var)))); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, push_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = push_node; + } + } + IrOpcode::procedures_call_nonwarp(ProceduresCallNonwarpFields { + proc, + next_step, + }) => { + let Some(nonwarped_specific_proc) = &*proc.nonwarped_specific_proc() else { + hq_bug!("tried to call_nonwarp with no non-warped proc") + }; + for arg_var in nonwarped_specific_proc + .arg_vars() + .try_borrow()? + .iter() + .rev() + .cloned() + { + let pop_node = + self.add_node(Some(StackOperation::Pop(VarTarget::Var(arg_var)))); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, pop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = pop_node; + } + next_steps.push(*next_step); + should_propagate_ssa = true; + break 'opcode_loop; + } + IrOpcode::hq_yield(HqYieldFields { mode }) => match mode { + YieldMode::Inline(step) => { + let step_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(step))); + graphs.insert(step_mut.try_borrow()?.id().into(), MaybeGraph::Started); + self.visit_step( + Rc::clone(&step_mut), + variable_maps, + graphs, + next_steps, + do_ssa, + )?; + graphs.insert(step_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); + } + YieldMode::None | YieldMode::Return => { + should_propagate_ssa = true; + step_ended_on_stop = true; + break 'opcode_loop; + } + YieldMode::Schedule(_) => (), // handled at bottom of loop + }, + _ => { + let opcode_node = self.add_node(Some(StackOperation::Opcode(opcode.clone()))); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, opcode_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = opcode_node; + } + } + if let Some(next_step) = opcode.yields_to_next_step() { + next_steps.push(next_step); + should_propagate_ssa = true; + break 'opcode_loop; + } + } + + if (step.try_borrow()?.used_non_inline() || step_ended_on_stop) + && step.try_borrow()?.context().warp + && let Some(proc_context) = maybe_proc_context.as_ref() + { + for ret_var in (*proc_context.ret_vars).borrow().iter().rev().cloned() { + let pop_node = self.add_node(Some(StackOperation::Pop(VarTarget::Var(ret_var)))); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, pop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = pop_node; + } + } + + // extra block so the `step_mut` mutable borrow gets dropped before + // `propagate_ssa` is called + { + let mut step_mut = step.try_borrow_mut()?; + let opcodes = step_mut.opcodes_mut(); + + // enact opcode replacements + for (index, opcode_replacement) in opcode_replacements { + *opcodes + .get_mut(index) + .ok_or_else(|| make_hq_bug!("opcode index out of bounds"))? = + opcode_replacement; + } + + // insert additional opcodes + for (index, additional_opcodes) in additional_opcodes.into_iter().rev() { + opcodes.splice(index..index, additional_opcodes); + } + } + + if should_propagate_ssa { + self.propagate_ssa(&step, variable_maps)?; + } + Ok(()) + } + + fn make_loop_prelude( + &self, + variable_maps: &mut VariableMaps, + maybe_proc_context: Option<&ProcContext>, + new_var_map: &BTreeMap, + ) -> HQResult> { + Ok(new_var_map + .iter() + .map(|(global_var, new_var)| { + let (var_access, accessed_var) = variable_maps + .use_current_ssa_for_global::<_, _, _, HQResult<_>>( + global_var, + |ssa_var| { + Ok(( + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(ssa_var.clone()), + local_read: RefCell::new(true), + }), + ssa_var.clone(), + )) + }, + |arg_index, arg_var| { + Ok(( + IrOpcode::procedures_argument(ProceduresArgumentFields { + index: arg_index, + arg_var: arg_var.clone(), + in_warped: true, + arg_vars: Rc::clone( + &maybe_proc_context + .as_ref() + .ok_or_else(|| { + make_hq_bug!( + "tried to access proc argument when proc \ + context was None" + ) + })? + .arg_vars, + ), + }), + arg_var.clone(), + )) + }, + || { + Ok(( + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(global_var.clone()), + local_read: RefCell::new(false), + }), + global_var.clone(), + )) + }, + )?; + let push_node = + self.add_node(Some(StackOperation::Push(VarTarget::Var(accessed_var)))); + let pop_node = + self.add_node(Some(StackOperation::Pop(VarTarget::Var(new_var.clone())))); + let exit_node = *self.exit_node().borrow(); + self.add_edge(exit_node, push_node, EdgeType::Forward); + self.add_edge(push_node, pop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = pop_node; + variable_maps + .ssa + .insert(global_var.clone(), new_var.clone()); + Ok([ + var_access, + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(new_var.clone()), + local_write: RefCell::new(true), + first_write: RefCell::new(true), + }), + ]) + }) + .collect::>>()? + .into_iter() + .flatten() + .collect()) + } + + fn loop_ssa( + &mut self, + step: &S, + variable_maps: &mut VariableMaps, + graphs: &mut BTreeMap, MaybeGraph>, + next_steps: &mut Vec, + opcode_replacements: &mut Vec<(usize, IrOpcode)>, + additional_opcodes: &mut Vec<(usize, Vec)>, + maybe_proc_context: Option<&ProcContext>, + i: usize, + ControlLoopFields { + first_condition, + condition, + body, + flip_if, + pre_body, + }: &ControlLoopFields, + do_ssa: bool, + ) -> HQResult<()> + where + S: Deref>, + { + let new_var_map: BTreeMap<_, _> = step + .try_borrow()? + .globally_scoped_variables()? + .map(|global_var| (global_var, RcVar::new_empty())) + .collect(); + if do_ssa { + additional_opcodes.push(( + i, + self.make_loop_prelude(variable_maps, maybe_proc_context, &new_var_map)?, + )); + } + if let Some(first_condition_step) = first_condition { + let first_condition_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(first_condition_step))); + graphs.insert( + first_condition_mut.try_borrow()?.id().into(), + MaybeGraph::Started, + ); + self.visit_step( + Rc::clone(&first_condition_mut), + // we don't need to keep track of this map as no variables should be set in the first condition + &mut variable_maps.clone(), + graphs, + next_steps, + do_ssa, + )?; + graphs.insert( + first_condition_mut.try_borrow()?.id().into(), + MaybeGraph::Inlined, + ); + let drop_node = self.add_node(Some(StackOperation::Drop)); // we consume the top item on the type stack as an i32.eqz + let end_node = *self.exit_node().borrow(); + self.add_edge(end_node, drop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = drop_node; + let first_cond_exit = drop_node; + + let header_node = self.add_node(None); + self.add_edge(first_cond_exit, header_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = header_node; + + let pre_body_mut = pre_body + .as_ref() + .map(|pre_body_real| -> HQResult<_> { + let pre_body_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(pre_body_real))); + let mut pre_body_variable_maps = variable_maps.clone(); + graphs.insert(pre_body_mut.try_borrow()?.id().into(), MaybeGraph::Started); + self.visit_step( + Rc::clone(&pre_body_mut), + &mut pre_body_variable_maps, + graphs, + next_steps, + do_ssa, + )?; + graphs.insert(pre_body_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); + Ok(pre_body_mut) + }) + .transpose()?; + + let body_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(body))); + let mut body_variable_maps = variable_maps.clone(); + graphs.insert(body_mut.try_borrow()?.id().into(), MaybeGraph::Started); + self.visit_step( + Rc::clone(&body_mut), + &mut body_variable_maps, + graphs, + next_steps, + do_ssa, + )?; + graphs.insert(body_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); + + let condition_mut: InlinedStep = Rc::new(Rc::unwrap_or_clone(Rc::clone(condition))); + graphs.insert(condition_mut.try_borrow()?.id().into(), MaybeGraph::Started); + self.visit_step( + Rc::clone(&condition_mut), + &mut body_variable_maps, + graphs, + next_steps, + do_ssa, + )?; + graphs.insert(condition_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); + let drop_node = self.add_node(Some(StackOperation::Drop)); + let end_node = *self.exit_node().borrow(); + self.add_edge(end_node, drop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = drop_node; + let mut cond_exit = drop_node; + + if do_ssa { + self.ssa_phi( + vec![(&condition_mut, body_variable_maps.ssa, &mut cond_exit)], + variable_maps, + &new_var_map, + )?; + } + + opcode_replacements.push(( + i, + IrOpcode::control_loop(ControlLoopFields { + body: body_mut, + first_condition: Some(first_condition_mut), + flip_if: *flip_if, + condition: condition_mut, + pre_body: pre_body_mut, + }), + )); + + self.add_edge(cond_exit, header_node, EdgeType::BackLink); + *self.exit_node().borrow_mut() = cond_exit; + } else { + let header_node = self.add_node(None); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, header_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = header_node; + + let condition_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(condition))); + let mut body_variable_maps = variable_maps.clone(); + graphs.insert(condition_mut.try_borrow()?.id().into(), MaybeGraph::Started); + self.visit_step( + Rc::clone(&condition_mut), + &mut body_variable_maps, + graphs, + next_steps, + do_ssa, + )?; + graphs.insert(condition_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); + let drop_node = self.add_node(Some(StackOperation::Drop)); + let end_node = *self.exit_node().borrow(); + self.add_edge(end_node, drop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = drop_node; + + let pre_body_mut = pre_body + .as_ref() + .map(|pre_body_real| -> HQResult<_> { + let pre_body_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(pre_body_real))); + let mut pre_body_variable_maps = variable_maps.clone(); + graphs.insert(pre_body_mut.try_borrow()?.id().into(), MaybeGraph::Started); + self.visit_step( + Rc::clone(&pre_body_mut), + &mut pre_body_variable_maps, + graphs, + next_steps, + do_ssa, + )?; + graphs.insert(pre_body_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); + Ok(pre_body_mut) + }) + .transpose()?; + + let body_mut = Rc::new(Rc::unwrap_or_clone(Rc::clone(body))); + graphs.insert(body_mut.try_borrow()?.id().into(), MaybeGraph::Started); + self.visit_step( + Rc::clone(&body_mut), + &mut body_variable_maps, + graphs, + next_steps, + do_ssa, + )?; + graphs.insert(body_mut.try_borrow()?.id().into(), MaybeGraph::Inlined); + let mut body_exit = *self.exit_node().borrow(); + + if do_ssa { + self.ssa_phi( + vec![(&body_mut, body_variable_maps.ssa, &mut body_exit)], + variable_maps, + &new_var_map, + )?; + } + + self.add_edge(body_exit, header_node, EdgeType::BackLink); + *self.exit_node().borrow_mut() = body_exit; + + opcode_replacements.push(( + i, + IrOpcode::control_loop(ControlLoopFields { + flip_if: *flip_if, + body: body_mut, + first_condition: None, + condition: condition_mut, + pre_body: pre_body_mut, + }), + )); + } + let loop_exit = *self.exit_node().borrow(); + let new_node = self.add_node(None); + self.add_edge(loop_exit, new_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = new_node; + + Ok(()) + } + + fn propagate_ssa(&self, step: &S, variable_maps: &VariableMaps) -> HQResult<()> + where + S: Deref>, + { + let post_yield = { + if let Some(last_op) = step.try_borrow()?.opcodes().last() + && matches!( + last_op, + IrOpcode::hq_yield(_) | IrOpcode::procedures_call_nonwarp(_) + ) + { + true + } else { + false + } + }; + let mut step_mut = step.try_borrow_mut()?; + let opcodes = step_mut.opcodes_mut(); + let yield_op = if post_yield { + #[expect(clippy::unwrap_used, reason = "guaranteed last element")] + Some(opcodes.pop().unwrap()) + } else { + None + }; + for (global_var, ssa_var) in &variable_maps.ssa { + let push_node = + self.add_node(Some(StackOperation::Push(VarTarget::Var(ssa_var.clone())))); + let pop_node = self.add_node(Some(StackOperation::Pop(VarTarget::Var( + global_var.clone(), + )))); + let last_node = *self.exit_node().borrow(); + self.add_edge(last_node, push_node, EdgeType::Forward); + self.add_edge(push_node, pop_node, EdgeType::Forward); + *self.exit_node().borrow_mut() = pop_node; + opcodes.extend([ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(ssa_var.clone()), + local_read: RefCell::new(true), + }), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(global_var.clone()), + local_write: RefCell::new(false), + first_write: RefCell::new(false), + }), + ]); + } + if post_yield { + #[expect(clippy::unwrap_used, reason = "guaranteed to be Some")] + opcodes.push(yield_op.unwrap()); + } + Ok(()) + } + + /// Insert an SSA "phi function"; that is, propagate any new SSAs to the outer scope, + /// at a so-called dominance frontier (where different paths meet) + fn ssa_phi( + &self, + mut ssa_blocks: Vec<(&InlinedStep, BTreeMap, &mut NodeIndex)>, + variable_maps: &mut VariableMaps, + ssa_write_map: &BTreeMap, + ) -> HQResult<()> { + let mut lower_ssas: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new(); + let write_map_was_empty = ssa_write_map.is_empty(); + + for (block, block_ssa, _) in &ssa_blocks { + for (global, block_local) in block_ssa { + lower_ssas + .entry(global.clone()) + .and_modify(|ssa_block_map| { + ssa_block_map.insert(Rc::clone(block), block_local.clone()); + }) + .or_insert_with(|| BTreeMap::from([(Rc::clone(block), block_local.clone())])); + } + } + + let mut block_exits: BTreeMap<_, _> = ssa_blocks + .iter_mut() + .map(|(block, _block_ssas, block_exit)| (Rc::clone(block), block_exit)) + .collect(); + + let mut block_var_writes = BTreeMap::new(); + + for (global, block_ssas) in lower_ssas { + let new_var = ssa_write_map + .get(&global) + .cloned() + .unwrap_or_else(RcVar::new_empty); + for block in block_exits.keys() { + if !block_ssas.contains_key(block) { + block_var_writes + .entry(Rc::clone(block)) + .or_insert_with(Vec::new) + .push(( + variable_maps + .ssa + .get(&global) + .cloned() + .map_or_else(|| (global.clone(), false), |var| (var, true)), + new_var.clone(), + )); + } + } + for (block, block_ssa) in &block_ssas { + block_var_writes + .entry(Rc::clone(block)) + .or_insert_with(Vec::new) + .push(((block_ssa.clone(), true), new_var.clone())); + } + variable_maps.ssa.insert(global.clone(), new_var.clone()); + } + + for (block, var_writes) in &block_var_writes { + let Some(block_exit) = block_exits.get_mut(block) else { + hq_bug!("couldn't find SSA block exit") + }; + let mut block_mut = block.try_borrow_mut()?; + let opcodes = block_mut.opcodes_mut(); + if !matches!( + opcodes.last(), + Some(IrOpcode::hq_yield(HqYieldFields { + mode: YieldMode::Return, + })) + ) { + opcodes.reserve_exact(var_writes.len() * 2); + for ((var_read, read_local), var_write) in var_writes { + let push_node = + self.add_node(Some(StackOperation::Push(VarTarget::Var(var_read.clone())))); + let pop_node = + self.add_node(Some(StackOperation::Pop(VarTarget::Var(var_write.clone())))); + self.add_edge(***block_exit, push_node, EdgeType::Forward); + self.add_edge(push_node, pop_node, EdgeType::Forward); + ***block_exit = pop_node; + + opcodes.append(&mut vec![ + IrOpcode::data_variable(DataVariableFields { + var: RefCell::new(var_read.clone()), + local_read: RefCell::new(*read_local), + }), + IrOpcode::data_setvariableto(DataSetvariabletoFields { + var: RefCell::new(var_write.clone()), + local_write: RefCell::new(true), + // TODO: might this actually be the first write? + first_write: RefCell::new(write_map_was_empty), + }), + ]); + } + } + } + + Ok(()) + } +} diff --git a/src/optimisation/variable_merging.rs b/src/optimisation/variable_merging.rs index 99740a5c..7f7016cd 100644 --- a/src/optimisation/variable_merging.rs +++ b/src/optimisation/variable_merging.rs @@ -170,7 +170,6 @@ fn build_liveness_events( where S: Deref>, { - // crate::log!("merging vars for step {}", step.try_borrow()?.id()); for block in step.try_borrow()?.opcodes().iter().rev() { #[expect( clippy::wildcard_enum_match_arm, @@ -460,18 +459,6 @@ pub fn merge_variables(proj: &Rc, _ssa_token: SSAToken) -> HQResult<( #[expect(clippy::mutable_key_type, reason = "hash depends only on immutable id")] let merged = build_merges_from_events(&events); merge_variables_in_step(step, &merged)?; - // crate::log!( - // "merging {} variables into {}", - // merged.len(), - // merged.values().collect::>().len(), - // ); - // crate::log!( - // "{{\n{}\n}}", - // merged - // .iter() - // .map(|(v1, v2)| format!("\t{v1} => {v2}")) - // .join(",\n") - // ); } Ok(()) diff --git a/src/wasm.rs b/src/wasm.rs index 1449ae7c..0300dd28 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -13,9 +13,3 @@ pub use project::{FinishedWasm, WasmProject}; pub use registries::{ GlobalExportable, GlobalMutable, Registries, StepsTable, StringsTable, ThreadsTable, }; - -/// the same as Into, but `const`. -#[must_use] -pub const fn f32_to_ieeef32(f: f32) -> wasm_encoder::Ieee32 { - unsafe { core::mem::transmute(u32::from_le_bytes(f.to_le_bytes())) } -} diff --git a/src/wasm/flags.rs b/src/wasm/flags.rs index 3f8af9c9..03288105 100644 --- a/src/wasm/flags.rs +++ b/src/wasm/flags.rs @@ -213,7 +213,6 @@ impl WasmFlags { #[wasm_bindgen(constructor)] #[must_use] pub fn new(wasm_features: Vec) -> Self { - // crate::log(format!("{wasm_features:?}").as_str()); Self { wasm_opt: Switch::On, string_type: if wasm_features.contains(&WasmFeature::JSStringBuiltins) { diff --git a/src/wasm/func.rs b/src/wasm/func.rs index f3ea8736..dff4206e 100644 --- a/src/wasm/func.rs +++ b/src/wasm/func.rs @@ -190,11 +190,9 @@ impl Instruction { )]) } Self::LazyGlobalGet(idx) => { - // crate::log!("global get {idx}. imported globals: {imported_global_count}"); Box::from([WInstruction::GlobalGet(idx + imported_global_count)]) } Self::LazyGlobalSet(idx) => { - // crate::log!("global get {idx}. imported globals: {imported_global_count}"); Box::from([WInstruction::GlobalSet(idx + imported_global_count)]) } Self::StaticFunctionCall(idx) => { @@ -310,19 +308,10 @@ impl StepFunc { } pub fn local_variable(&self, var: &RcVar) -> HQResult { - // crate::log!("accessing local variable for variable {}", var.id()); - // crate::log!( - // "existing local variables: {:?}", - // self.local_variables.borrow() - // ); Ok( match self.local_variables.try_borrow_mut()?.entry(var.clone()) { - btree_map::Entry::Occupied(entry) => { - // crate::log("local already exists, returning that"); - *entry.get() - } + btree_map::Entry::Occupied(entry) => *entry.get(), btree_map::Entry::Vacant(entry) => { - // crate::log("making a new local for variable"); let index = self.local(WasmProject::ir_type_to_wasm(*var.possible_types())?)?; entry.insert(index); index diff --git a/src/wasm/project.rs b/src/wasm/project.rs index b962e831..a33db190 100644 --- a/src/wasm/project.rs +++ b/src/wasm/project.rs @@ -121,17 +121,6 @@ impl WasmProject { start_func.instruction(&Instruction::End); - // self.registries().tables().register_override::( - // "strings".into(), - // TableOptions { - // element_type: RefType::EXTERNREF, - // min: 0, - // // TODO: use js string imports for preknown strings - // max: None, - // init: None, - // }, - // )?; - self.registries() .external_functions() .clone() @@ -280,18 +269,7 @@ impl WasmProject { .clone() .finish(&mut tables, &mut exports); - // crate::log!( - // "imported func count: {}, static func count: {}", - // self.imported_func_count()?, - // self.static_func_count()? - // ); - exports.export("memory", ExportKind::Memory, 0); - // exports.export( - // "noop", - // ExportKind::Func, - // self.imported_func_count()? + self.static_func_count()?, - // ); self.registries().globals().clone().finish( &mut globals, @@ -752,25 +730,6 @@ impl WasmProject { }) .collect(), ); - // StepFunc::compile_step( - // Rc::new(Step::new_empty( - // Rc::downgrade(ir_project), - // true, - // Rc::new(IrTarget::new( - // false, - // BTreeMap::default(), - // BTreeMap::default(), - // Weak::new(), - // RefCell::new(BTreeMap::default()), - // 0, - // Box::new([]), - // )), - // )), - // &steps, - // Rc::clone(®istries), - // flags, - // )?; - // compile every step for (i, step) in ir_project.steps().try_borrow()?.iter().enumerate() { StepFunc::compile_step( step, @@ -821,11 +780,6 @@ mod tests { #[test] fn empty_project_is_valid_wasm() { let registries = Rc::new(Registries::default()); - // let project = Rc::new(IrProject::new( - // BTreeMap::default(), - // BTreeMap::default(), - // Box::from([]), - // )); let steps = Rc::new(RefCell::new(Vec::new())); let project = WasmProject { flags: WasmFlags::new(all_wasm_features()), diff --git a/src/wasm/registries/functions.rs b/src/wasm/registries/functions.rs index 886e9851..72f487b4 100644 --- a/src/wasm/registries/functions.rs +++ b/src/wasm/registries/functions.rs @@ -1,5 +1,9 @@ #![allow(clippy::cast_possible_wrap, reason = "can't use try_into in const")] +mod mark_waiting_flag; +mod pen_colour; +mod spawn_threads; + use wasm_encoder::{ CodeSection, EntityType, ExportKind, ExportSection, Function, FunctionSection, ImportSection, Instruction as WInstruction, ValType, @@ -102,767 +106,9 @@ impl StaticFunctionRegistry { } pub mod static_functions { - use mem_layout::{sprite as sprite_layout, stage as stage_layout}; - use wasm_encoder::{ - AbstractHeapType, BlockType as WasmBlockType, HeapType, MemArg, RefType, ValType, + pub use super::mark_waiting_flag::MarkWaitingFlag; + pub use super::pen_colour::{UpdatePenColorFromHSV, UpdatePenColorFromRGB}; + pub use super::spawn_threads::{ + SpawnNewThread, SpawnNewThreadOverride, SpawnThreadInStack, SpawnThreadInStackOverride, }; - use wasm_gen::wasm_const; - - use super::{MaybeStaticFunction, StaticFunction}; - use crate::prelude::*; - use crate::wasm::{f32_to_ieeef32, mem_layout}; - - /// Mark a waiting flag as done. - /// - /// This is designed to be exported (as `"mark_waiting_flag"`) and called by JS. - /// - /// Takes 1 parameter: - /// - A nonnull struct with a single i8 field. - /// - /// Override with one u32, the single-field i8 struct type index - pub struct MarkWaitingFlag; - impl NamedRegistryItem for MarkWaitingFlag { - const VALUE: MaybeStaticFunction = MaybeStaticFunction { - static_function: None, - maybe_populate: || None, - }; - } - pub type MarkWaitingFlagOverride = u32; - impl NamedRegistryItemOverride for MarkWaitingFlag { - fn r#override(i8_struct_ty: u32) -> MaybeStaticFunction { - MaybeStaticFunction { - static_function: Some(StaticFunction { - export: Some("mark_waiting_flag".into()), - instructions: Box::from(wasm_const![ - LocalGet(0), - I32Const(1), - StructSet { - struct_type_index: i8_struct_ty, - field_index: 0 - }, - End, - ] as &[_]), - params: Box::new([ValType::Ref(RefType { - nullable: false, - heap_type: HeapType::Concrete(i8_struct_ty), - })]), - returns: Box::new([]), - locals: Box::new([]), - }), - maybe_populate: || None, - } - } - } - - /// Spawns a new thread in the same stack (i.e. a thread that yields back to the current - /// thread once it completes.) - /// - /// Takes 4 parameters: - /// - i32 - the current thread index - /// - step funcref - the step to spawn - /// - structref - the structref to pass to the step being spawned - /// - step funcref - the step to return to after - /// - /// Override with: - /// - u32 - the index of the step func type - /// - u32 - the index of the stack struct type - /// - u32 - the index of the stack array type - /// - u32 - the index of the thread struct type - /// - u32 - the index of the threads table - pub struct SpawnThreadInStack; - impl NamedRegistryItem for SpawnThreadInStack { - const VALUE: MaybeStaticFunction = MaybeStaticFunction { - static_function: None, - maybe_populate: || None, - }; - } - pub type SpawnThreadInStackOverride = (u32, u32, u32, u32, u32); - impl NamedRegistryItemOverride - for SpawnThreadInStack - { - fn r#override( - (func_ty, stack_struct_type, stack_array_type, thread_struct_type, threads_table): SpawnThreadInStackOverride, - ) -> MaybeStaticFunction { - MaybeStaticFunction { - static_function: Some(StaticFunction { - export: None, - instructions: Box::from(wasm_const![ - LocalGet(1), - LocalGet(2), - StructNew(stack_struct_type), - LocalSet(4), - LocalGet(0), - TableGet(threads_table), - RefAsNonNull, - LocalTee(5), - StructGet { - struct_type_index: thread_struct_type, - field_index: 1, - }, - LocalGet(5), - StructGet { - struct_type_index: thread_struct_type, - field_index: 0, - }, - LocalGet(4), - // todo: consider the case where we need to resize the array - ArraySet(stack_array_type), - LocalGet(5), - StructGet { - struct_type_index: thread_struct_type, - field_index: 1, - }, - LocalGet(5), - StructGet { - struct_type_index: thread_struct_type, - field_index: 0, - }, - I32Const(1), - I32Sub, - ArrayGet(stack_array_type), - LocalGet(3), - StructSet { - struct_type_index: stack_struct_type, - field_index: 0, - }, - LocalGet(5), - LocalGet(5), - StructGet { - struct_type_index: thread_struct_type, - field_index: 0, - }, - I32Const(1), - I32Add, - StructSet { - struct_type_index: thread_struct_type, - field_index: 0, - }, - End - ] as &[_]), - params: Box::from([ - ValType::I32, - ValType::Ref(RefType { - nullable: false, - heap_type: HeapType::Concrete(func_ty), - }), - ValType::Ref(RefType { - nullable: true, - heap_type: wasm_encoder::HeapType::Abstract { - shared: false, - ty: AbstractHeapType::Struct, - }, - }), - ValType::Ref(RefType { - nullable: false, - heap_type: HeapType::Concrete(func_ty), - }), - ]), - returns: Box::from([]), - locals: Box::from([ - ValType::Ref(RefType { - nullable: false, - heap_type: HeapType::Concrete(stack_struct_type), - }), - ValType::Ref(RefType { - nullable: false, - heap_type: HeapType::Concrete(thread_struct_type), - }), - ]), - }), - maybe_populate: || None, - } - } - } - - /// Spawn a new thread with the provided step function. This does not call it - /// immediately, instead leaving that for the scheduler or calling function to do so. - /// - /// Takes 2 parameters: - /// - step funcref - the step to spawn - /// - ref null struct - the stack struct to spawn it with - /// - /// Override with: - /// - u32 - the index of the step func type - /// - u32 - the index of the stack struct type - /// - u32 - the index of the stack array type - /// - u32 - the index of the thread struct type - /// - u32 - the index of the threads table - pub struct SpawnNewThread; - impl NamedRegistryItem for SpawnNewThread { - const VALUE: MaybeStaticFunction = MaybeStaticFunction { - static_function: None, - maybe_populate: || None, - }; - } - pub type SpawnNewThreadOverride = (u32, u32, u32, u32, u32); - impl NamedRegistryItemOverride for SpawnNewThread { - fn r#override( - (func_ty, stack_struct_ty, stack_array_ty, thread_struct_ty, threads_table_index): SpawnNewThreadOverride, - ) -> MaybeStaticFunction { - MaybeStaticFunction { - static_function: Some(StaticFunction { - export: None, - params: Box::from([ - ValType::Ref(RefType { - nullable: false, - heap_type: HeapType::Concrete(func_ty), - }), - ValType::Ref(RefType { - nullable: true, - heap_type: wasm_encoder::HeapType::Abstract { - shared: false, - ty: AbstractHeapType::Struct, - }, - }), - ]), - returns: Box::from([]), - locals: Box::from([]), - instructions: (wasm_const![ - I32Const(1), - LocalGet(0), - LocalGet(1), - StructNew(stack_struct_ty), - // todo: play around with initial size of stack array - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - RefNull(HeapType::Concrete(stack_struct_ty)), - ArrayNewFixed { - array_size: 8, - array_type_index: stack_array_ty, - }, - StructNew(thread_struct_ty), - I32Const(1), - TableGrow(threads_table_index), - Drop, - End, - ] as &[_]) - .into(), - }), - maybe_populate: || None, - } - } - } - - index_counter! { - hsv2rgb_locals - SPRITE_INDEX - MEM_POS - HUE SAT VAL - REGION - REMAINDER - P Q T - R G B - VAL_F - } - - /// Updates the stored RGBA pen colour from the HSV colour. - /// - /// Takes 1 paramter, an i32 corresponding to the target index - /// - /// Not overridable. - pub struct UpdatePenColorFromHSV; - impl NamedRegistryItem for UpdatePenColorFromHSV { - const VALUE: MaybeStaticFunction = MaybeStaticFunction { - static_function: None, - maybe_populate: || { - Some(StaticFunction { - export: None, - params: Box::from([ValType::I32]), - returns: Box::from([]), - locals: Box::from({ - const PARAMS_NUM: usize = 1; - let mut locals = - [ValType::I32; hsv2rgb_locals::BLOCK_SIZE as usize - PARAMS_NUM]; - locals[hsv2rgb_locals::VAL_F as usize - PARAMS_NUM] = ValType::F32; - locals - }), - instructions: (wasm_const![ - // hsv->rgb based off of https://stackoverflow.com/a/14733008 - LocalGet(hsv2rgb_locals::SPRITE_INDEX), - I32Const(sprite_layout::BLOCK_SIZE as i32), - I32Mul, - I32Const(stage_layout::BLOCK_SIZE as i32), - I32Add, - LocalTee(hsv2rgb_locals::MEM_POS), // position in memory of sprite info - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR.into(), - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(2.55)), - F32Mul, - I32TruncF32S, - LocalSet(hsv2rgb_locals::HUE), - LocalGet(hsv2rgb_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_SATURATION.into(), - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(2.55)), - F32Mul, - I32TruncF32S, - LocalSet(hsv2rgb_locals::SAT), // saturation ∈ [0, 256) - LocalGet(hsv2rgb_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_BRIGHTNESS.into(), - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(2.55)), - F32Mul, - I32TruncF32S, - LocalSet(hsv2rgb_locals::VAL), // value ∈ [0, 256) - LocalGet(hsv2rgb_locals::MEM_POS), - F32Const(f32_to_ieeef32(100.0)), - LocalGet(hsv2rgb_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_TRANSPARENCY.into(), - align: 2, - memory_index: 0, - }), // transparency ∈ [0, 100] - F32Sub, - F32Const(f32_to_ieeef32(100.0)), - F32Div, // alpha ∈ [0, 1] - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_A.into(), - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_A.into(), - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(0.01)), - F32Lt, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_A.into(), - align: 2, - memory_index: 0, - }), - Return, // if alpha is 0, return (it is already set to 0 so it doesn't matter what r, g & b are) - End, - LocalGet(hsv2rgb_locals::SAT), - I32Eqz, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::VAL), - F32ConvertI32S, - F32Const(f32_to_ieeef32(255.0)), - F32Div, - LocalSet(hsv2rgb_locals::VAL_F), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::VAL_F), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_R.into(), - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::VAL_F), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_G.into(), - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::VAL_F), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_B.into(), - align: 2, - memory_index: 0, - }), - Return, - End, - LocalGet(hsv2rgb_locals::HUE), - I32Const(43), - I32DivU, - LocalSet(hsv2rgb_locals::REGION), // 'region' - LocalGet(hsv2rgb_locals::HUE), - I32Const(43), - I32RemU, - I32Const(6), - I32Mul, - LocalSet(hsv2rgb_locals::REMAINDER), // 'remainder' - I32Const(255), - LocalGet(hsv2rgb_locals::SAT), - I32Sub, - LocalGet(hsv2rgb_locals::VAL), - I32Mul, - I32Const(8), - I32ShrU, - LocalSet(hsv2rgb_locals::P), // 'p' - I32Const(255), - LocalGet(hsv2rgb_locals::REMAINDER), - LocalGet(hsv2rgb_locals::SAT), - I32Mul, - I32Const(8), - I32ShrU, - I32Sub, - LocalGet(hsv2rgb_locals::VAL), - I32Mul, - I32Const(8), - I32ShrU, - LocalSet(hsv2rgb_locals::Q), // 'q' - I32Const(255), - I32Const(255), - LocalGet(hsv2rgb_locals::REMAINDER), - I32Sub, - LocalGet(hsv2rgb_locals::SAT), - I32Mul, - I32Const(8), - I32ShrU, - I32Sub, - LocalGet(hsv2rgb_locals::VAL), - I32Mul, - I32Const(8), - I32ShrU, - LocalSet(hsv2rgb_locals::T), // 't' - LocalGet(hsv2rgb_locals::REGION), - I32Eqz, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::T), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(1), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::Q), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(2), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::T), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(3), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::Q), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(4), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::T), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::REGION), - I32Const(5), - I32Eq, - If(WasmBlockType::Empty), - LocalGet(hsv2rgb_locals::VAL), - LocalSet(hsv2rgb_locals::R), - LocalGet(hsv2rgb_locals::P), - LocalSet(hsv2rgb_locals::G), - LocalGet(hsv2rgb_locals::Q), - LocalSet(hsv2rgb_locals::B), - End, - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::R), - F32ConvertI32S, - F32Const(f32_to_ieeef32(255.0)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_R.into(), - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::G), - F32ConvertI32S, - F32Const(f32_to_ieeef32(255.0)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_G.into(), - align: 2, - memory_index: 0, - }), - LocalGet(hsv2rgb_locals::MEM_POS), - LocalGet(hsv2rgb_locals::B), - F32ConvertI32S, - F32Const(f32_to_ieeef32(255.0)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR_B.into(), - align: 2, - memory_index: 0, - }), - End, - ] as &[_]) - .into(), - }) - }, - }; - } - - index_counter! { - rgb2hsv_locals - SPRITE_INDEX - MEM_POS - R G B A - RGB_MIN RGB_MAX - HUE SAT - } - - /// Updates the stored HSV pen colour from the RGBA colour. - /// - /// Takes one parameter, an i32 corresponding to the target index. - /// - /// Not overridable. - pub struct UpdatePenColorFromRGB; - impl NamedRegistryItem for UpdatePenColorFromRGB { - const VALUE: MaybeStaticFunction = MaybeStaticFunction { - static_function: None, - maybe_populate: || { - Some(StaticFunction { - export: None, - params: Box::from([ValType::I32]), - returns: Box::from([]), - locals: Box::from({ - const PARAMS_NUM: usize = 1; - let mut locals = - [ValType::I32; rgb2hsv_locals::BLOCK_SIZE as usize - PARAMS_NUM]; - locals[rgb2hsv_locals::A as usize - PARAMS_NUM] = ValType::F32; - locals - }), - instructions: (wasm_const![ - // rgb->hsv based off of https://stackoverflow.com/a/14733008 - LocalGet(rgb2hsv_locals::SPRITE_INDEX), - I32Const(sprite_layout::BLOCK_SIZE as i32), - I32Mul, - I32Const(stage_layout::BLOCK_SIZE as i32), - I32Add, - LocalTee(rgb2hsv_locals::MEM_POS), // position in memory of sprite info - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_R.into(), - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(255.0)), - F32Mul, - I32TruncF32S, - LocalSet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_G.into(), - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(255.0)), - F32Mul, - I32TruncF32S, - LocalSet(rgb2hsv_locals::G), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_B.into(), - align: 2, - memory_index: 0, - }), - F32Const(f32_to_ieeef32(255.0)), - F32Mul, - I32TruncF32S, - LocalSet(rgb2hsv_locals::B), - LocalGet(rgb2hsv_locals::MEM_POS), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Load(MemArg { - offset: sprite_layout::PEN_COLOR_A.into(), - align: 2, - memory_index: 0, - }), // transparency ∈ [0, 100] - F32Const(f32_to_ieeef32(100.0)), - F32Mul, // alpha ∈ [0, 1] - LocalTee(rgb2hsv_locals::A), - F32Const(f32_to_ieeef32(100.0)), - F32Sub, - F32Store(MemArg { - offset: sprite_layout::PEN_TRANSPARENCY.into(), - align: 2, - memory_index: 0, - }), - // we don't need to check for alpha=0 to shortcircuit, because scratch doesn't allow - // alpha=0 for rgb colours - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - I32LtS, - Select, - LocalTee(rgb2hsv_locals::RGB_MIN), - LocalGet(rgb2hsv_locals::B), - LocalGet(rgb2hsv_locals::RGB_MIN), - LocalGet(rgb2hsv_locals::B), - I32LtS, - Select, - LocalSet(rgb2hsv_locals::RGB_MIN), - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - I32GtS, - Select, - LocalTee(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::B), - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::B), - I32GtS, - Select, - LocalSet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::MEM_POS), - LocalGet(rgb2hsv_locals::RGB_MAX), - F32ConvertI32S, - F32Const(f32_to_ieeef32(2.55)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_BRIGHTNESS.into(), - align: 2, - memory_index: 0, - }), - LocalGet(rgb2hsv_locals::RGB_MAX), - I32Eqz, - If(WasmBlockType::Empty), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR.into(), - align: 2, - memory_index: 0, - }), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_SATURATION.into(), - align: 2, - memory_index: 0, - }), - Return, - End, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::RGB_MIN), - I32Sub, - I32Const(255), - I32Mul, - LocalGet(rgb2hsv_locals::RGB_MAX), - I32DivS, - LocalTee(rgb2hsv_locals::SAT), - I32Eqz, - If(WasmBlockType::Empty), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR.into(), - align: 2, - memory_index: 0, - }), - LocalGet(rgb2hsv_locals::MEM_POS), - F32Const(f32_to_ieeef32(0.0)), - F32Store(MemArg { - offset: sprite_layout::PEN_SATURATION.into(), - align: 2, - memory_index: 0, - }), - Return, - End, - LocalGet(rgb2hsv_locals::MEM_POS), - LocalGet(rgb2hsv_locals::SAT), - F32ConvertI32S, - F32Const(f32_to_ieeef32(2.55)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_SATURATION.into(), - align: 2, - memory_index: 0, - }), - LocalGet(rgb2hsv_locals::MEM_POS), - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::R), - I32Eq, - If(WasmBlockType::Result(ValType::I32)), - LocalGet(rgb2hsv_locals::G), - LocalGet(rgb2hsv_locals::B), - I32Sub, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::RGB_MIN), - I32Sub, - I32DivS, - I32Const(43), - I32Mul, - Else, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::G), - I32Eq, - If(WasmBlockType::Result(ValType::I32)), - LocalGet(rgb2hsv_locals::B), - LocalGet(rgb2hsv_locals::R), - I32Sub, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::RGB_MIN), - I32Sub, - I32DivS, - I32Const(43), - I32Mul, - I32Const(85), - I32Add, - Else, - LocalGet(rgb2hsv_locals::R), - LocalGet(rgb2hsv_locals::G), - I32Sub, - LocalGet(rgb2hsv_locals::RGB_MAX), - LocalGet(rgb2hsv_locals::RGB_MIN), - I32Sub, - I32DivS, - I32Const(43), - I32Mul, - I32Const(171), - I32Add, - End, - End, - F32ConvertI32S, - F32Const(f32_to_ieeef32(2.55)), - F32Div, - F32Store(MemArg { - offset: sprite_layout::PEN_COLOR.into(), - align: 2, - memory_index: 0, - }), - End, - ] as &[_]) - .into(), - }) - }, - }; - } } diff --git a/src/wasm/registries/functions/mark_waiting_flag.rs b/src/wasm/registries/functions/mark_waiting_flag.rs new file mode 100644 index 00000000..f2e3751c --- /dev/null +++ b/src/wasm/registries/functions/mark_waiting_flag.rs @@ -0,0 +1,47 @@ +use wasm_encoder::{HeapType, RefType, ValType}; +use wasm_gen::wasm_const; + +use super::{MaybeStaticFunction, StaticFunction}; +use crate::prelude::*; + +/// Mark a waiting flag as done. +/// +/// This is designed to be exported (as `"mark_waiting_flag"`) and called by JS. +/// +/// Takes 1 parameter: +/// - A nonnull struct with a single i8 field. +/// +/// Override with one u32, the single-field i8 struct type index +pub struct MarkWaitingFlag; +impl NamedRegistryItem for MarkWaitingFlag { + const VALUE: MaybeStaticFunction = MaybeStaticFunction { + static_function: None, + maybe_populate: || None, + }; +} +pub type MarkWaitingFlagOverride = u32; +impl NamedRegistryItemOverride for MarkWaitingFlag { + fn r#override(i8_struct_ty: u32) -> MaybeStaticFunction { + MaybeStaticFunction { + static_function: Some(StaticFunction { + export: Some("mark_waiting_flag".into()), + instructions: Box::from(wasm_const![ + LocalGet(0), + I32Const(1), + StructSet { + struct_type_index: i8_struct_ty, + field_index: 0 + }, + End, + ] as &[_]), + params: Box::new([ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(i8_struct_ty), + })]), + returns: Box::new([]), + locals: Box::new([]), + }), + maybe_populate: || None, + } + } +} diff --git a/src/wasm/registries/functions/pen_colour.rs b/src/wasm/registries/functions/pen_colour.rs new file mode 100644 index 00000000..3d584d40 --- /dev/null +++ b/src/wasm/registries/functions/pen_colour.rs @@ -0,0 +1,526 @@ +use mem_layout::{sprite as sprite_layout, stage as stage_layout}; +use wasm_encoder::{BlockType as WasmBlockType, MemArg, ValType}; +use wasm_gen::wasm_const; + +use super::{MaybeStaticFunction, StaticFunction}; +use crate::prelude::*; +use crate::wasm::mem_layout; + +index_counter! { + hsv2rgb_locals + SPRITE_INDEX + MEM_POS + HUE SAT VAL + REGION + REMAINDER + P Q T + R G B + VAL_F +} + +/// Updates the stored RGBA pen colour from the HSV colour. +/// +/// Takes 1 paramter, an i32 corresponding to the target index +/// +/// Not overridable. +pub struct UpdatePenColorFromHSV; +impl NamedRegistryItem for UpdatePenColorFromHSV { + const VALUE: MaybeStaticFunction = MaybeStaticFunction { + static_function: None, + maybe_populate: || { + Some(StaticFunction { + export: None, + params: Box::from([ValType::I32]), + returns: Box::from([]), + locals: Box::from({ + const PARAMS_NUM: usize = 1; + let mut locals = + [ValType::I32; hsv2rgb_locals::BLOCK_SIZE as usize - PARAMS_NUM]; + locals[hsv2rgb_locals::VAL_F as usize - PARAMS_NUM] = ValType::F32; + locals + }), + instructions: (wasm_const![ + // hsv->rgb based off of https://stackoverflow.com/a/14733008 + LocalGet(hsv2rgb_locals::SPRITE_INDEX), + I32Const(sprite_layout::BLOCK_SIZE as i32), + I32Mul, + I32Const(stage_layout::BLOCK_SIZE as i32), + I32Add, + LocalTee(hsv2rgb_locals::MEM_POS), // position in memory of sprite info + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR.into(), + align: 2, + memory_index: 0, + }), + F32Const(2.55.into()), + F32Mul, + I32TruncF32S, + LocalSet(hsv2rgb_locals::HUE), + LocalGet(hsv2rgb_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_SATURATION.into(), + align: 2, + memory_index: 0, + }), + F32Const(2.55.into()), + F32Mul, + I32TruncF32S, + LocalSet(hsv2rgb_locals::SAT), // saturation ∈ [0, 256) + LocalGet(hsv2rgb_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_BRIGHTNESS.into(), + align: 2, + memory_index: 0, + }), + F32Const(2.55.into()), + F32Mul, + I32TruncF32S, + LocalSet(hsv2rgb_locals::VAL), // value ∈ [0, 256) + LocalGet(hsv2rgb_locals::MEM_POS), + F32Const(100.0.into()), + LocalGet(hsv2rgb_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_TRANSPARENCY.into(), + align: 2, + memory_index: 0, + }), // transparency ∈ [0, 100] + F32Sub, + F32Const(100.0.into()), + F32Div, // alpha ∈ [0, 1] + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_A.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_A.into(), + align: 2, + memory_index: 0, + }), + F32Const(0.01.into()), + F32Lt, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::MEM_POS), + F32Const(0.0.into()), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_A.into(), + align: 2, + memory_index: 0, + }), + Return, // if alpha is 0, return (it is already set to 0 so it doesn't matter what r, g & b are) + End, + LocalGet(hsv2rgb_locals::SAT), + I32Eqz, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::VAL), + F32ConvertI32S, + F32Const(255.0.into()), + F32Div, + LocalSet(hsv2rgb_locals::VAL_F), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::VAL_F), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_R.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::VAL_F), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_G.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::VAL_F), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_B.into(), + align: 2, + memory_index: 0, + }), + Return, + End, + LocalGet(hsv2rgb_locals::HUE), + I32Const(43), + I32DivU, + LocalSet(hsv2rgb_locals::REGION), // 'region' + LocalGet(hsv2rgb_locals::HUE), + I32Const(43), + I32RemU, + I32Const(6), + I32Mul, + LocalSet(hsv2rgb_locals::REMAINDER), // 'remainder' + I32Const(255), + LocalGet(hsv2rgb_locals::SAT), + I32Sub, + LocalGet(hsv2rgb_locals::VAL), + I32Mul, + I32Const(8), + I32ShrU, + LocalSet(hsv2rgb_locals::P), // 'p' + I32Const(255), + LocalGet(hsv2rgb_locals::REMAINDER), + LocalGet(hsv2rgb_locals::SAT), + I32Mul, + I32Const(8), + I32ShrU, + I32Sub, + LocalGet(hsv2rgb_locals::VAL), + I32Mul, + I32Const(8), + I32ShrU, + LocalSet(hsv2rgb_locals::Q), // 'q' + I32Const(255), + I32Const(255), + LocalGet(hsv2rgb_locals::REMAINDER), + I32Sub, + LocalGet(hsv2rgb_locals::SAT), + I32Mul, + I32Const(8), + I32ShrU, + I32Sub, + LocalGet(hsv2rgb_locals::VAL), + I32Mul, + I32Const(8), + I32ShrU, + LocalSet(hsv2rgb_locals::T), // 't' + LocalGet(hsv2rgb_locals::REGION), + I32Eqz, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::T), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(1), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::Q), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(2), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::T), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(3), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::Q), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(4), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::T), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::REGION), + I32Const(5), + I32Eq, + If(WasmBlockType::Empty), + LocalGet(hsv2rgb_locals::VAL), + LocalSet(hsv2rgb_locals::R), + LocalGet(hsv2rgb_locals::P), + LocalSet(hsv2rgb_locals::G), + LocalGet(hsv2rgb_locals::Q), + LocalSet(hsv2rgb_locals::B), + End, + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::R), + F32ConvertI32S, + F32Const(255.0.into()), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_R.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::G), + F32ConvertI32S, + F32Const(255.0.into()), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_G.into(), + align: 2, + memory_index: 0, + }), + LocalGet(hsv2rgb_locals::MEM_POS), + LocalGet(hsv2rgb_locals::B), + F32ConvertI32S, + F32Const(255.0.into()), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR_B.into(), + align: 2, + memory_index: 0, + }), + End, + ] as &[_]) + .into(), + }) + }, + }; +} + +index_counter! { + rgb2hsv_locals + SPRITE_INDEX + MEM_POS + R G B A + RGB_MIN RGB_MAX + SAT +} + +/// Updates the stored HSV pen colour from the RGBA colour. +/// +/// Takes one parameter, an i32 corresponding to the target index. +/// +/// Not overridable. +pub struct UpdatePenColorFromRGB; +impl NamedRegistryItem for UpdatePenColorFromRGB { + const VALUE: MaybeStaticFunction = MaybeStaticFunction { + static_function: None, + maybe_populate: || { + Some(StaticFunction { + export: None, + params: Box::from([ValType::I32]), + returns: Box::from([]), + locals: Box::from({ + const PARAMS_NUM: usize = 1; + let mut locals = + [ValType::I32; rgb2hsv_locals::BLOCK_SIZE as usize - PARAMS_NUM]; + locals[rgb2hsv_locals::A as usize - PARAMS_NUM] = ValType::F32; + locals + }), + instructions: (wasm_const![ + // rgb->hsv based off of https://stackoverflow.com/a/14733008 + LocalGet(rgb2hsv_locals::SPRITE_INDEX), + I32Const(sprite_layout::BLOCK_SIZE as i32), + I32Mul, + I32Const(stage_layout::BLOCK_SIZE as i32), + I32Add, + LocalTee(rgb2hsv_locals::MEM_POS), // position in memory of sprite info + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_R.into(), + align: 2, + memory_index: 0, + }), + F32Const(255.0.into()), + F32Mul, + I32TruncF32S, + LocalSet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_G.into(), + align: 2, + memory_index: 0, + }), + F32Const(255.0.into()), + F32Mul, + I32TruncF32S, + LocalSet(rgb2hsv_locals::G), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_B.into(), + align: 2, + memory_index: 0, + }), + F32Const(255.0.into()), + F32Mul, + I32TruncF32S, + LocalSet(rgb2hsv_locals::B), + LocalGet(rgb2hsv_locals::MEM_POS), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Load(MemArg { + offset: sprite_layout::PEN_COLOR_A.into(), + align: 2, + memory_index: 0, + }), // transparency ∈ [0, 100] + F32Const(100.0.into()), + F32Mul, // alpha ∈ [0, 1] + LocalTee(rgb2hsv_locals::A), + F32Const(100.0.into()), + F32Sub, + F32Store(MemArg { + offset: sprite_layout::PEN_TRANSPARENCY.into(), + align: 2, + memory_index: 0, + }), + // we don't need to check for alpha=0 to shortcircuit, because scratch doesn't allow + // alpha=0 for rgb colours + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + I32LtS, + Select, + LocalTee(rgb2hsv_locals::RGB_MIN), + LocalGet(rgb2hsv_locals::B), + LocalGet(rgb2hsv_locals::RGB_MIN), + LocalGet(rgb2hsv_locals::B), + I32LtS, + Select, + LocalSet(rgb2hsv_locals::RGB_MIN), + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + I32GtS, + Select, + LocalTee(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::B), + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::B), + I32GtS, + Select, + LocalSet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::MEM_POS), + LocalGet(rgb2hsv_locals::RGB_MAX), + F32ConvertI32S, + F32Const(2.55.into()), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_BRIGHTNESS.into(), + align: 2, + memory_index: 0, + }), + LocalGet(rgb2hsv_locals::RGB_MAX), + I32Eqz, + If(WasmBlockType::Empty), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Const(0.0.into()), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR.into(), + align: 2, + memory_index: 0, + }), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Const(0.0.into()), + F32Store(MemArg { + offset: sprite_layout::PEN_SATURATION.into(), + align: 2, + memory_index: 0, + }), + Return, + End, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::RGB_MIN), + I32Sub, + I32Const(255), + I32Mul, + LocalGet(rgb2hsv_locals::RGB_MAX), + I32DivS, + LocalTee(rgb2hsv_locals::SAT), + I32Eqz, + If(WasmBlockType::Empty), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Const(0.0.into()), + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR.into(), + align: 2, + memory_index: 0, + }), + LocalGet(rgb2hsv_locals::MEM_POS), + F32Const(0.0.into()), + F32Store(MemArg { + offset: sprite_layout::PEN_SATURATION.into(), + align: 2, + memory_index: 0, + }), + Return, + End, + LocalGet(rgb2hsv_locals::MEM_POS), + LocalGet(rgb2hsv_locals::SAT), + F32ConvertI32S, + F32Const(2.55.into()), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_SATURATION.into(), + align: 2, + memory_index: 0, + }), + LocalGet(rgb2hsv_locals::MEM_POS), + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::R), + I32Eq, + If(WasmBlockType::Result(ValType::I32)), + LocalGet(rgb2hsv_locals::G), + LocalGet(rgb2hsv_locals::B), + I32Sub, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::RGB_MIN), + I32Sub, + I32DivS, + I32Const(43), + I32Mul, + Else, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::G), + I32Eq, + If(WasmBlockType::Result(ValType::I32)), + LocalGet(rgb2hsv_locals::B), + LocalGet(rgb2hsv_locals::R), + I32Sub, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::RGB_MIN), + I32Sub, + I32DivS, + I32Const(43), + I32Mul, + I32Const(85), + I32Add, + Else, + LocalGet(rgb2hsv_locals::R), + LocalGet(rgb2hsv_locals::G), + I32Sub, + LocalGet(rgb2hsv_locals::RGB_MAX), + LocalGet(rgb2hsv_locals::RGB_MIN), + I32Sub, + I32DivS, + I32Const(43), + I32Mul, + I32Const(171), + I32Add, + End, + End, + F32ConvertI32S, + F32Const(2.55.into()), + F32Div, + F32Store(MemArg { + offset: sprite_layout::PEN_COLOR.into(), + align: 2, + memory_index: 0, + }), + End, + ] as &[_]) + .into(), + }) + }, + }; +} diff --git a/src/wasm/registries/functions/spawn_threads.rs b/src/wasm/registries/functions/spawn_threads.rs new file mode 100644 index 00000000..b10185c2 --- /dev/null +++ b/src/wasm/registries/functions/spawn_threads.rs @@ -0,0 +1,198 @@ +use wasm_encoder::{AbstractHeapType, HeapType, RefType, ValType}; +use wasm_gen::wasm_const; + +use super::{MaybeStaticFunction, StaticFunction}; +use crate::prelude::*; + +/// Spawns a new thread in the same stack (i.e. a thread that yields back to the current +/// thread once it completes.) +/// +/// Takes 4 parameters: +/// - i32 - the current thread index +/// - step funcref - the step to spawn +/// - structref - the structref to pass to the step being spawned +/// - step funcref - the step to return to after +/// +/// Override with: +/// - u32 - the index of the step func type +/// - u32 - the index of the stack struct type +/// - u32 - the index of the stack array type +/// - u32 - the index of the thread struct type +/// - u32 - the index of the threads table +pub struct SpawnThreadInStack; +impl NamedRegistryItem for SpawnThreadInStack { + const VALUE: MaybeStaticFunction = MaybeStaticFunction { + static_function: None, + maybe_populate: || None, + }; +} +pub type SpawnThreadInStackOverride = (u32, u32, u32, u32, u32); +impl NamedRegistryItemOverride + for SpawnThreadInStack +{ + fn r#override( + (func_ty, stack_struct_type, stack_array_type, thread_struct_type, threads_table): SpawnThreadInStackOverride, + ) -> MaybeStaticFunction { + MaybeStaticFunction { + static_function: Some(StaticFunction { + export: None, + instructions: Box::from(wasm_const![ + LocalGet(1), + LocalGet(2), + StructNew(stack_struct_type), + LocalSet(4), + LocalGet(0), + TableGet(threads_table), + RefAsNonNull, + LocalTee(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 1, + }, + LocalGet(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 0, + }, + LocalGet(4), + // todo: consider the case where we need to resize the array + ArraySet(stack_array_type), + LocalGet(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 1, + }, + LocalGet(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 0, + }, + I32Const(1), + I32Sub, + ArrayGet(stack_array_type), + LocalGet(3), + StructSet { + struct_type_index: stack_struct_type, + field_index: 0, + }, + LocalGet(5), + LocalGet(5), + StructGet { + struct_type_index: thread_struct_type, + field_index: 0, + }, + I32Const(1), + I32Add, + StructSet { + struct_type_index: thread_struct_type, + field_index: 0, + }, + End + ] as &[_]), + params: Box::from([ + ValType::I32, + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(func_ty), + }), + ValType::Ref(RefType { + nullable: true, + heap_type: wasm_encoder::HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Struct, + }, + }), + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(func_ty), + }), + ]), + returns: Box::from([]), + locals: Box::from([ + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(stack_struct_type), + }), + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(thread_struct_type), + }), + ]), + }), + maybe_populate: || None, + } + } +} + +/// Spawn a new thread with the provided step function. This does not call it +/// immediately, instead leaving that for the scheduler or calling function to do so. +/// +/// Takes 2 parameters: +/// - step funcref - the step to spawn +/// - ref null struct - the stack struct to spawn it with +/// +/// Override with: +/// - u32 - the index of the step func type +/// - u32 - the index of the stack struct type +/// - u32 - the index of the stack array type +/// - u32 - the index of the thread struct type +/// - u32 - the index of the threads table +pub struct SpawnNewThread; +impl NamedRegistryItem for SpawnNewThread { + const VALUE: MaybeStaticFunction = MaybeStaticFunction { + static_function: None, + maybe_populate: || None, + }; +} +pub type SpawnNewThreadOverride = (u32, u32, u32, u32, u32); +impl NamedRegistryItemOverride for SpawnNewThread { + fn r#override( + (func_ty, stack_struct_ty, stack_array_ty, thread_struct_ty, threads_table_index): SpawnNewThreadOverride, + ) -> MaybeStaticFunction { + MaybeStaticFunction { + static_function: Some(StaticFunction { + export: None, + params: Box::from([ + ValType::Ref(RefType { + nullable: false, + heap_type: HeapType::Concrete(func_ty), + }), + ValType::Ref(RefType { + nullable: true, + heap_type: wasm_encoder::HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Struct, + }, + }), + ]), + returns: Box::from([]), + locals: Box::from([]), + instructions: (wasm_const![ + I32Const(1), + LocalGet(0), + LocalGet(1), + StructNew(stack_struct_ty), + // todo: play around with initial size of stack array + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + RefNull(HeapType::Concrete(stack_struct_ty)), + ArrayNewFixed { + array_size: 8, + array_type_index: stack_array_ty, + }, + StructNew(thread_struct_ty), + I32Const(1), + TableGrow(threads_table_index), + Drop, + End, + ] as &[_]) + .into(), + }), + maybe_populate: || None, + } + } +} diff --git a/src/wasm/registries/globals.rs b/src/wasm/registries/globals.rs index 8fa191e0..77111ae2 100644 --- a/src/wasm/registries/globals.rs +++ b/src/wasm/registries/globals.rs @@ -45,10 +45,6 @@ impl GlobalRegistry { imported_global_count + globals.len(), ); } - // let actual_initial = match &*key { - // "noop_func" => ConstExpr::ref_func(imported_function_count + static_function_count), - // _ => suggested_initial, - // }; globals.global( GlobalType { val_type: ty, diff --git a/src/wasm/registries/lists.rs b/src/wasm/registries/lists.rs index b6828feb..28873994 100644 --- a/src/wasm/registries/lists.rs +++ b/src/wasm/registries/lists.rs @@ -101,7 +101,6 @@ impl ListRegistry { ]; let array_global = self.globals().register( - // format!("__rcvar_{:p}", Rc::as_ptr(&var.0)).into(), format!("__rclist_list_{}", list.id()).into(), ( ValType::Ref(RefType { diff --git a/src/wasm/registries/strings.rs b/src/wasm/registries/strings.rs index 73738ca8..00b357db 100644 --- a/src/wasm/registries/strings.rs +++ b/src/wasm/registries/strings.rs @@ -12,7 +12,6 @@ impl StringRegistry { N: TryFrom, >::Error: fmt::Debug, { - // crate::log!("registering string: {}", string); self.0.register_default(string) } diff --git a/src/wasm/registries/tables.rs b/src/wasm/registries/tables.rs index 35546452..d9158277 100644 --- a/src/wasm/registries/tables.rs +++ b/src/wasm/registries/tables.rs @@ -38,10 +38,6 @@ impl TableRegistry { if let Some(export_key) = export_name { exports.export(export_key, ExportKind::Table, tables.len()); } - // let maybe_init = match &*key { - // "threads" => Some(ConstExpr::ref_func(imports.len())), - // _ => init, - // }; if let Some(init) = maybe_init { tables.table_with_init( TableType { diff --git a/src/wasm/registries/variables.rs b/src/wasm/registries/variables.rs index 304f0407..9a1a5e4b 100644 --- a/src/wasm/registries/variables.rs +++ b/src/wasm/registries/variables.rs @@ -46,7 +46,6 @@ impl VariableRegistry { >::Error: fmt::Debug, { self.globals().register( - // format!("__rcvar_{:p}", Rc::as_ptr(&var.0)).into(), format!("__rcvar_{}", var.id()).into(), ( WasmProject::ir_type_to_wasm(*var.possible_types())?, diff --git a/wasm-gen/src/lib.rs b/wasm-gen/src/lib.rs index 2c6cffbb..9787c094 100644 --- a/wasm-gen/src/lib.rs +++ b/wasm-gen/src/lib.rs @@ -290,14 +290,7 @@ pub fn wasm(input: TokenStream) -> TokenStream { let __strings_table_index: u32 = func .registries() .tables() - .register::( - // "strings".into(), crate::wasm::TableOptions { - // element_type: RefType::EXTERNREF, - // min: 0, - // max: None, - // init: None, - // } - )?; + .register::()?; #(#conditions) else * else { unreachable!() }