@triflare/fx 2.1.0-beta.2 → 2.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -5
- package/product.yml +1 -1
- package/src/catalog.js +914 -0
- package/src/cli/commands/catalog.js +154 -20
- package/src/cli/format.js +52 -0
- package/src/cli/reporter.js +8 -0
- package/src/compiler.js +37 -2
- package/src/decompiler.js +27 -0
- package/src/vm.js +39 -11
- package/vendor/pm-vm-dist/.fx-build.json +7 -0
- package/vendor/pm-vm-dist/LICENSE +373 -0
- package/vendor/pm-vm-dist/NOTICE +6 -0
- package/vendor/pm-vm-dist/dist/iframe-extension-worker.js +1 -0
- package/vendor/pm-vm-dist/dist/js/extension-worker/extension-worker.7aa98c98b43d49535069.js +1 -0
- package/vendor/pm-vm-dist/dist/media/music/00765be80e1ec0073c7d855eac123b96.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/040abc33bc843ccea91acc82b370a2a5.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/059f32937ff4e15834ad94972c058386.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/0ae12e3be02439395202850a9c469930.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/0ae841a4034e4847205ff93589fe38b7.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/101c38be253a79f22164915a7b41a485.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/1c0f11f5e3bc3a290e3011a863acbf19.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/1e35fc4bf222acbb373be13bef7b494c.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/27470aa23024c67716d745d09d1eeb0d.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/2a2c19ccfb24fd1f611133f46859f697.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/2f42c08774845c95c5afe6aa2011660d.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/317cfa5845ae8684414e36059aa217cf.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/32ab039554155f1cc982d06b17c1b268.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/368b67706d2601ccdb29ba0ae4a06c7d.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/418e78ff0f0202a85f8e51ddceb6383b.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/44fede772a621f62516dafeec7ba6e76.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/45b502e33be78aca1c76962d8f0c9799.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/4efb38b83c696fde2043ad3770fd6cf5.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/555849284aa06b4716e47d7e12341ab4.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/6072de860d1aed016b4b82379510efc7.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/612b52d7a8538850a26cc9a6adda0451.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/6469f82ff2d352bc85e7528635ca6b98.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/66f88d71bbebdff3c79c315946b77516.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/70ce80e25dd12856990938c55f576862.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/79fb30eddc4dd8362cabe7dc80b59da6.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/7f0e67fcb804175c4169eabf81d21040.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/832a2a264fdeb69057ad7208584a49b5.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/83f40654a5eb9dc81f854c2b6f83422c.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/8414e157e2b98d7d4462330c8c4b1cc2.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/8d02b8a9547b47c8506d6c15c913acc1.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/912efc3e11a27b50b879a7ad3bad2eab.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/92771972e8ea5e5e76566218bd193471.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/92e9bc4ec854ae2c3157169fc5ca47a4.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/9b36fd7513f2eeab9876bc75c98262c6.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/9c571ca77e0ff0d1071c39941677febc.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/9d3f26ebd92e02a921b031785dafd6d6.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/9f769e9f239b0a1c048486b24cd9e15b.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/a5a68e9168e8e677cc15494e8680538b.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/a6a3f99085f00a94f2fb5aebfeee2879.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/a6caebbce7ac630fa45cd12fedc920c0.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/b5e23b90e61aaf37f5de98e9ca81fdbb.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/bc4d077743d9bc06d58065fd764f4019.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/bef29ef069663beb6280b70aac6705de.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/c2ba808baa06dac52c1787790396e54c.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/ca1b5393c07468dc2be541c24c3cc687.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/cad1541317d9ef4443a8c6325c7df3be.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/cde6740cc2c1d45a800e1237cc86f9a1.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/d448a74cb6caebc801e921ecdfd0c5d8.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/d5e7e578ff2d4a9b14c0d51f33a24299.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/df6c9e67211654be18df433951b021b0.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/dfe095471b15f0935cde612b52c91a3c.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/e64996bbc174611c2ad3d04a1434f7a0.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/e9e0e78519e67c9a17563711949e4684.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/eb290db5208d65da146b41d34f22f008.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/ebe739a8e09e50ea00c5f1afacc15d6a.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/ed88e2831000ca0e9dceabb95a346597.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/f059a67cea01cb6e49ba358c05d658c9.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/f3a9a9ae967dd5a330f29aa94d5c4680.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/f7e43cdb9928ae9999bc623f2087ee1a.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/fc4058e26c5069cbf08e741771d1e210.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/media/music/fd598784d15d0a4673327e341eb40d3b.mp3 +1 -0
- package/vendor/pm-vm-dist/dist/scratch-vm.js +187 -0
- package/vendor/pm-vm-dist/dist/static/assets/0043f23189fb3cbf2021a88959961ece.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/00d2dff9d5ece15a67074374b2027590.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/02996322134b61a0344527cddc61f9bd.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/06e9863f95061349f2ba2e0fb0b58ddc.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/0966e5defb1ae50e569fbbc7ebe7f22a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/09995475180591a6c64c8744fbfdf4d8.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/0b18f02669375ad2ee80bef8d07ea7ec.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/0dcd27b60c185811b19f59d56eaa12f6.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/0e8ed599d9875c77d52db36de2b2b33f.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/103fe904046c35630c600cb122492d78.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/10af80546707afae5507edb828dd35a0.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/11c35683a60415bf62ee1a7324a25848.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/129b99ac19024c35228d855f42fb852b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1980303ffc6f5c17c601a9e5992aea77.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1a064f9a752824e781ccf79ab66850ed.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1a87a73db1ba0193283680e37b35754e.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1c4f445589ea1ce7d954fc601f787f95.svg +58 -0
- package/vendor/pm-vm-dist/dist/static/assets/1c8f54c61adf1cee25299833093395f3.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1c9630b745711a0b829841e8555d76e4.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1d1e22600289d252ebda0b9924bf7d3a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/1e7ed7b14b056ebd80f434143846fb4e.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/2b0cfb89b2c8d1badb03136fcf911c3b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/2b7b80de8884cc3cf584aa0deb86a59b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/3682b35e452a7f4a5a61167d1632e24a.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/368c582b289e75a3c5299e954c2ad735.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/36ed01ff8cd7eccf19f2606b56f6cd9f.svg +18 -0
- package/vendor/pm-vm-dist/dist/static/assets/39563f3b38b1fdb805323b1f69dace54.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/3fbd690bdae84637e34b8b933a164e3d.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/4240a79d13ecf2792f825457194c96d9.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/44c364962f4cb85aef2cdec4f0e01fd4.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/48d66af328a669a1c84c5dc87a4e9a3b.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/4c5757837d14e9fca439064c62617ba1.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/4c768843c9fa6593074231cd762b83f8.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/4ec6148233578e86736548eb8b9952e7.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/51a75dd50afd0bc6022dfc86455be19c.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/57f0f65052d9dac78ee66b11cc696b6a.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5888c4ba6b8a0767f8be5bbc81994397.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/58d62765bbaa69620eb5bcd2c1cbbebb.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5c6533a34175f61a9cab627f671111e1.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5e0704b815761d38242f1b961ce9ef69.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5e80f6caeb6323606a203fbb248fe18a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5f46c8dc3b8cc2296eb73f013dfce3db.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/5fe2f38d9b66e0a55b49e717fcd5c001.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/61edf73198617b2326d37f232b8abd6f.otf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/638bc6bde253241454993ae12273970b.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/64bcfe1f13fce38e2ea26280157ff2ce.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/7337f194b0a727f4be98902487dfb615.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/74496d9086d97aaeeafb3085e9957668.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/76e8f03f3f2ac00ab16dd4f2afee63c4.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/78a74345ee778afbd6abe637b4f69efc.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/7a7fdb40997804d4778e2a12bd7599f7.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/7c5a16e0627c64fd62ec597bb72b879d.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/827c57667d965940792a82d82345e075.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/83857346b7c2e6a351c69377e82a3606.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/83f2d48d0c7d1e6be8a509d4d2c6d0e5.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/87a3a62c0627e925b92d3ede2f62b04f.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8996930d8e83f63cc3341172205df460.otf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8bad9bd9d9a0d53b4a0f4e69bf02764a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8c0c02f16572b730d422f96a3ce53e7f.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8ca9997e9ed7534a8d0f349a6125bd24.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8d83d454d2c42614a203322393c23a5b.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8f3259bb74d811177e8e65dcbea39ed4.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/8fdeee6df0b53509b7f9d623cab482b3.svg +1 -0
- package/vendor/pm-vm-dist/dist/static/assets/90eaf6469dbf3a808a3d9739cc77358d.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/942d5ca1a892e7dc5156d0ae7b438c1b.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/94b3a496d095dd3b3cfbaabb84a190fd.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/961bfad11616b6f5c951c58499cf267a.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/97aa37df83e2c5c2effa09d6622cd3a7.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/9ad12a9de73f49fb645902987027eb2b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/9ec3449723269a806ec6eaba0f8f6cef.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/9f8e3a1db63c7a8fd976beca7e3546c3.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a02821bc5d19d616d43d55ac10779101.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a05bbf6359407ad7950e115edab178d9.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a0b130dbafdc7958b73d162239053173.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a61fc8393abec3c0d1e813fc01f252ee.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/a7c08a472cc8b1c4e0c45ae9b030b414.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/b52b83f63e8482255d46f40ea454fa01.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/b6a8e4a96ffcd83154ad2c5cbcb52751.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/b700cf6f9c368ce49851d287ddd3b63c.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/bacc13779ca01580b89f39dd2118c22a.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/bb59f04e7f0aabe8e142ac1607a03dd6.otf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/bd16b79cacee7f23835efba88ed4aa67.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/c36dd943607003a3229aca77b49d32de.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/c44ce3bfa465281e765f197d6c3ee7ea.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/c60f3f06c5fb4d49df7236aeb2f31a9d.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/d434438e5d193118e4aad3a846e1c7ba.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/d4b3ce804680eb7d58f6accc7436e807.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/d6214ab9abf22f5d5dee060cb72f637f.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/d72b98474fc83d6268886779339e242e.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/da1a6ebcd442e9d867b31fb21ce6a289.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/da3126c6a70c4ef84192c33f99996354.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/e27f7cdf42df8b768345ae83fecffe4b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/e6180253665b8248f685c6d96881f75b.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/eb64e706eb7a3b324ba50d3279a980b1.ttf +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/ecb1cbda3b9c4f29ab971734d97e3faf.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/ed2a96fbdb476efe419246982e357121.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/edd84717633475a8d3db34087123314a.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f04f7b0f673ced51710157ce16e49276.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f28b1ecd6fccd6218a837c14b1109da7.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f382f43d667b784d1bcd8cfe7aa6fff0.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f8d8b9d9f1aa45900b786980418219b1.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f974293a11768662b53444715e3439ed.png +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/f9868658d8483a6dbcd600e0226dfeef.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/fad59dd220562492e6722aa8a97f9369.mp3 +0 -0
- package/vendor/pm-vm-dist/dist/static/assets/fc383956e315b7ee2bd8dd09de311701.mp3 +0 -0
- package/vendor/pm-vm-dist/package.json +3 -0
|
@@ -1,15 +1,104 @@
|
|
|
1
|
-
import { CATALOG
|
|
1
|
+
import { CATALOG } from "../../catalog.js";
|
|
2
2
|
import { loadProjectState } from "../context.js";
|
|
3
|
+
import { plural, terminalColumns } from "../format.js";
|
|
4
|
+
|
|
5
|
+
// Language order for the built-in categories; extension groups follow
|
|
6
|
+
// alphabetically so their blocks stay together at the end of the listing.
|
|
7
|
+
const CATEGORY_ORDER = [
|
|
8
|
+
"motion",
|
|
9
|
+
"looks",
|
|
10
|
+
"sound",
|
|
11
|
+
"sensing",
|
|
12
|
+
"operators",
|
|
13
|
+
"control",
|
|
14
|
+
"events",
|
|
15
|
+
"data",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
// One-character kind marker, so a name alone still says how the block is used.
|
|
19
|
+
const KIND_MARKS = {
|
|
20
|
+
command: " ",
|
|
21
|
+
reporter: "→",
|
|
22
|
+
boolean: "?",
|
|
23
|
+
hat: "▸",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** `SCREAMING_SNAKE_CASE` name for a catalog argument. */
|
|
27
|
+
function argumentName(name) {
|
|
28
|
+
return name
|
|
29
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
30
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
|
|
31
|
+
.toUpperCase();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Positional arguments a block takes, in the order the compiler fills them. */
|
|
35
|
+
function argumentNames(entry) {
|
|
36
|
+
// `pm.*` are argument reporters whose VALUE field is the reporter's own
|
|
37
|
+
// label ("is compiled?"), not a parameter the caller supplies.
|
|
38
|
+
if (entry.opcode?.startsWith("argument_reporter")) return [];
|
|
39
|
+
if (entry.kind === "hat")
|
|
40
|
+
return (entry.args || []).map((arg) => arg.field || arg.input);
|
|
41
|
+
return [
|
|
42
|
+
...Object.keys(entry.fields || {}),
|
|
43
|
+
...Object.entries(entry.inputs || {})
|
|
44
|
+
.filter(([, spec]) => spec.type !== "substack")
|
|
45
|
+
.map(([name]) => name),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** `category.block(ARG, ...)` call shape shown in the listing. */
|
|
50
|
+
function signature(name, entry) {
|
|
51
|
+
const args = argumentNames(entry).map(argumentName);
|
|
52
|
+
return `${name}(${args.join(", ")})`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function mark(entry) {
|
|
56
|
+
return KIND_MARKS[entry.kind] ?? " ";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Group block names by category, in reading order. */
|
|
60
|
+
function groupNames(names) {
|
|
61
|
+
const groups = new Map();
|
|
62
|
+
for (const name of names) {
|
|
63
|
+
const group = name.split(".")[0];
|
|
64
|
+
if (!groups.has(group)) groups.set(group, []);
|
|
65
|
+
groups.get(group).push(name);
|
|
66
|
+
}
|
|
67
|
+
const rank = (group) => {
|
|
68
|
+
const index = CATEGORY_ORDER.indexOf(group);
|
|
69
|
+
return index < 0 ? CATEGORY_ORDER.length : index;
|
|
70
|
+
};
|
|
71
|
+
return new Map(
|
|
72
|
+
[...groups.entries()].sort(
|
|
73
|
+
([left], [right]) =>
|
|
74
|
+
rank(left) - rank(right) || left.localeCompare(right),
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
3
78
|
|
|
4
79
|
export const catalogCommand = {
|
|
5
80
|
name: "catalog",
|
|
6
81
|
aliases: ["l", "blocks"],
|
|
7
82
|
group: "Core Commands:",
|
|
8
83
|
summary: "List available blocks",
|
|
9
|
-
description: `List block names, including blocks from extensions
|
|
10
|
-
project.`,
|
|
84
|
+
description: `List block names grouped by category, including blocks from extensions
|
|
85
|
+
configured in the project.`,
|
|
11
86
|
arguments: ["[dir]"],
|
|
12
|
-
|
|
87
|
+
options: [
|
|
88
|
+
{
|
|
89
|
+
flags: "--category <name>",
|
|
90
|
+
description: "Only list one category, such as motion or pen",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
flags: "--search <text>",
|
|
94
|
+
description: "Only list blocks whose name contains the text",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
examples: [
|
|
98
|
+
"fx catalog",
|
|
99
|
+
"fx catalog --category motion",
|
|
100
|
+
"fx catalog --search glide",
|
|
101
|
+
],
|
|
13
102
|
async run({ args, cwd, options, reporter, product, prompts }) {
|
|
14
103
|
const state = await loadProjectState({
|
|
15
104
|
input: args[0],
|
|
@@ -19,24 +108,69 @@ project.`,
|
|
|
19
108
|
prompts,
|
|
20
109
|
});
|
|
21
110
|
const merged = { ...CATALOG, ...state.catalog };
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
")
|
|
111
|
+
const category =
|
|
112
|
+
typeof options.category === "string"
|
|
113
|
+
? options.category.toLowerCase()
|
|
114
|
+
: null;
|
|
115
|
+
const search =
|
|
116
|
+
typeof options.search === "string" ? options.search.toLowerCase() : null;
|
|
117
|
+
const names = Object.keys(merged)
|
|
118
|
+
.filter(
|
|
119
|
+
(name) => !category || name.split(".")[0].toLowerCase() === category,
|
|
120
|
+
)
|
|
121
|
+
.filter((name) => !search || name.toLowerCase().includes(search))
|
|
122
|
+
.sort();
|
|
123
|
+
const selected = Object.fromEntries(
|
|
124
|
+
names.map((name) => [name, merged[name]]),
|
|
31
125
|
);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
126
|
+
|
|
127
|
+
if (names.length === 0) {
|
|
128
|
+
const filters = [
|
|
129
|
+
category && `--category "${options.category}"`,
|
|
130
|
+
search && `--search "${options.search}"`,
|
|
131
|
+
]
|
|
132
|
+
.filter(Boolean)
|
|
133
|
+
.join(" and ");
|
|
134
|
+
reporter.info(`No blocks match ${filters}.`);
|
|
135
|
+
reporter.hint(`run "${product.command} catalog" to see every block`);
|
|
136
|
+
return selected;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const groups = groupNames(names);
|
|
140
|
+
let title = `${product.name} catalog — ${plural(names.length, "block")}`;
|
|
141
|
+
if (category) title += ` · ${category}`;
|
|
142
|
+
else if (search) title += ` matching "${options.search}"`;
|
|
143
|
+
else title += ` in ${plural(groups.size, "group")}`;
|
|
144
|
+
reporter.heading(title);
|
|
145
|
+
if (!category && !search)
|
|
146
|
+
reporter.hint("narrow with --category <name> or --search <text>");
|
|
147
|
+
|
|
148
|
+
const width = terminalColumns();
|
|
149
|
+
for (const [group, entries] of groups) {
|
|
150
|
+
reporter.text("");
|
|
151
|
+
reporter.item(group, "", { width: 0, suffix: `· ${entries.length}` });
|
|
152
|
+
if (reporter.verbose) {
|
|
153
|
+
for (const name of entries) {
|
|
154
|
+
const entry = merged[name];
|
|
155
|
+
reporter.item(
|
|
156
|
+
`${mark(entry)} ${signature(name, entry)}`,
|
|
157
|
+
`-> ${entry.opcode} [${entry.kind}]`,
|
|
158
|
+
{
|
|
159
|
+
width: 44,
|
|
160
|
+
suffix: entry.extension ? `(ext ${entry.extension})` : "",
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
reporter.columns(
|
|
166
|
+
entries.map(
|
|
167
|
+
(name) => `${mark(merged[name])} ${signature(name, merged[name])}`,
|
|
168
|
+
),
|
|
169
|
+
{ width },
|
|
170
|
+
);
|
|
171
|
+
}
|
|
38
172
|
}
|
|
39
173
|
|
|
40
|
-
return
|
|
174
|
+
return selected;
|
|
41
175
|
},
|
|
42
176
|
};
|
package/src/cli/format.js
CHANGED
|
@@ -29,3 +29,55 @@ export function truncate(text, max) {
|
|
|
29
29
|
const value = String(text);
|
|
30
30
|
return value.length > max ? `${value.slice(0, max)}…` : value;
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
/** Usable terminal width, clamped so narrow and huge terminals stay readable. */
|
|
34
|
+
export function terminalColumns(
|
|
35
|
+
stream = process.stdout,
|
|
36
|
+
{ fallback = 80, min = 40, max = 120 } = {},
|
|
37
|
+
) {
|
|
38
|
+
const width = stream?.columns;
|
|
39
|
+
if (!Number.isFinite(width) || width <= 0) return fallback;
|
|
40
|
+
return Math.min(max, Math.max(min, width));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Lay items out in aligned columns that fit `width`. Entries wider than one
|
|
45
|
+
* cell get a line of their own, so a single long name does not collapse the
|
|
46
|
+
* whole listing into one column. Trailing whitespace is removed.
|
|
47
|
+
*/
|
|
48
|
+
export function formatColumns(
|
|
49
|
+
items,
|
|
50
|
+
{ width = 80, indent = 2, gap = 2, cellWidth = 38 } = {},
|
|
51
|
+
) {
|
|
52
|
+
if (items.length === 0) return [];
|
|
53
|
+
const available = Math.max(width - indent, 1);
|
|
54
|
+
const columns = Math.max(
|
|
55
|
+
1,
|
|
56
|
+
Math.min(4, Math.floor((available + gap) / (cellWidth + gap))),
|
|
57
|
+
);
|
|
58
|
+
const cell = Math.floor((available - gap * (columns - 1)) / columns);
|
|
59
|
+
const lines = [];
|
|
60
|
+
let row = [];
|
|
61
|
+
|
|
62
|
+
const flush = () => {
|
|
63
|
+
if (row.length === 0) return;
|
|
64
|
+
const cells = row.map((text, index) =>
|
|
65
|
+
index === row.length - 1 ? text : text.padEnd(cell),
|
|
66
|
+
);
|
|
67
|
+
lines.push(`${" ".repeat(indent)}${cells.join(" ".repeat(gap))}`.trimEnd());
|
|
68
|
+
row = [];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
for (const item of items) {
|
|
72
|
+
const text = String(item);
|
|
73
|
+
if (text.length > cell) {
|
|
74
|
+
flush();
|
|
75
|
+
lines.push(`${" ".repeat(indent)}${text}`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
row.push(text);
|
|
79
|
+
if (row.length === columns) flush();
|
|
80
|
+
}
|
|
81
|
+
flush();
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
package/src/cli/reporter.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { styleText } from "node:util";
|
|
10
10
|
import { brandText } from "../brand.js";
|
|
11
|
+
import { formatColumns } from "./format.js";
|
|
11
12
|
import { createTheme } from "./theme.js";
|
|
12
13
|
|
|
13
14
|
const STATUS = {
|
|
@@ -145,6 +146,13 @@ export function createReporter({
|
|
|
145
146
|
stdout.write(` ${theme.accent(padded)}${detail}${note}\n`);
|
|
146
147
|
},
|
|
147
148
|
|
|
149
|
+
/** Compact multi-column rows for long block and file listings. */
|
|
150
|
+
columns(items, { width = 80, indent = 2, gap = 2 } = {}) {
|
|
151
|
+
if (!human) return;
|
|
152
|
+
for (const line of formatColumns(items, { width, indent, gap }))
|
|
153
|
+
stdout.write(`${line}\n`);
|
|
154
|
+
},
|
|
155
|
+
|
|
148
156
|
/** Plain block of text with no decoration. */
|
|
149
157
|
text(message) {
|
|
150
158
|
if (!human) return;
|
package/src/compiler.js
CHANGED
|
@@ -45,6 +45,8 @@ const NUMBER_TYPES = {
|
|
|
45
45
|
const TERMINAL_OPCODES = new Set([
|
|
46
46
|
"procedures_return",
|
|
47
47
|
"control_delete_this_clone",
|
|
48
|
+
"control_exitLoop",
|
|
49
|
+
"control_continueLoop",
|
|
48
50
|
]);
|
|
49
51
|
|
|
50
52
|
let seed = 0n;
|
|
@@ -778,6 +780,9 @@ export class Compiler {
|
|
|
778
780
|
if (block.opcode === "procedures_return") return "`return`";
|
|
779
781
|
if (block.opcode === "control_delete_this_clone")
|
|
780
782
|
return "`control.deleteThisClone()`";
|
|
783
|
+
if (block.opcode === "control_exitLoop") return "`control.escapeLoop()`";
|
|
784
|
+
if (block.opcode === "control_continueLoop")
|
|
785
|
+
return "`control.continueLoop()`";
|
|
781
786
|
if (block.opcode === "control_stop") {
|
|
782
787
|
const option = block.fields.STOP_OPTION
|
|
783
788
|
? block.fields.STOP_OPTION[0]
|
|
@@ -1034,14 +1039,17 @@ export class Compiler {
|
|
|
1034
1039
|
*/
|
|
1035
1040
|
emitCall(ctx, node, parentId) {
|
|
1036
1041
|
const segments = node.segments;
|
|
1042
|
+
const entry = this.catalog[node.name];
|
|
1037
1043
|
|
|
1038
1044
|
// List method: `items.push(x)`, `items.at(i)`, ...
|
|
1039
1045
|
if (segments.length === 2) {
|
|
1040
1046
|
const listInfo = ctx.resolveList(segments[0]);
|
|
1041
|
-
|
|
1047
|
+
// Canonical list methods take precedence; aliases resolve in emitListCall
|
|
1048
|
+
// only when the name does not identify a catalog block.
|
|
1049
|
+
if (listInfo && (Object.hasOwn(LIST_OPS, segments[1]) || !entry))
|
|
1050
|
+
return this.emitListCall(ctx, node, listInfo, parentId);
|
|
1042
1051
|
}
|
|
1043
1052
|
|
|
1044
|
-
const entry = this.catalog[node.name];
|
|
1045
1053
|
if (entry) return this.emitCatalogCall(ctx, node, entry, parentId);
|
|
1046
1054
|
|
|
1047
1055
|
if (segments.length === 1 && ctx.funcs.has(node.name)) {
|
|
@@ -1098,6 +1106,33 @@ export class Compiler {
|
|
|
1098
1106
|
for (const name of inputNames) {
|
|
1099
1107
|
const spec = entry.inputs[name];
|
|
1100
1108
|
if (spec.type === "substack") continue;
|
|
1109
|
+
// Variable and list references are stored as fields in Scratch JSON
|
|
1110
|
+
// even though they read as positional arguments in Fx source.
|
|
1111
|
+
if (spec.type === "variable" || spec.type === "list") {
|
|
1112
|
+
if (node.args[argIndex] === undefined)
|
|
1113
|
+
this.fail(
|
|
1114
|
+
`${node.name} requires a ${spec.type} name for ${name}`,
|
|
1115
|
+
node.loc,
|
|
1116
|
+
);
|
|
1117
|
+
const refName = String(this.literalValue(node.args[argIndex]));
|
|
1118
|
+
const resolved =
|
|
1119
|
+
spec.type === "list"
|
|
1120
|
+
? ctx.resolveList(refName)
|
|
1121
|
+
: ctx.resolveValue(refName);
|
|
1122
|
+
if (
|
|
1123
|
+
!resolved ||
|
|
1124
|
+
(spec.type === "list"
|
|
1125
|
+
? resolved.kind !== "list"
|
|
1126
|
+
: resolved.kind !== "var")
|
|
1127
|
+
)
|
|
1128
|
+
this.fail(
|
|
1129
|
+
`Unknown ${spec.type} ${JSON.stringify(refName)} for ${node.name}`,
|
|
1130
|
+
node.loc,
|
|
1131
|
+
);
|
|
1132
|
+
fields[name] = [resolved.name, resolved.id];
|
|
1133
|
+
argIndex += 1;
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1101
1136
|
if (node.args[argIndex] !== undefined) {
|
|
1102
1137
|
inputs[name] = this.buildInputForSpec(spec, node.args[argIndex], id);
|
|
1103
1138
|
argIndex += 1;
|
package/src/decompiler.js
CHANGED
|
@@ -18,7 +18,15 @@ const MENU_OPCODES = new Set([
|
|
|
18
18
|
"looks_backdrops",
|
|
19
19
|
"sound_sounds_menu",
|
|
20
20
|
"control_create_clone_of_menu",
|
|
21
|
+
"control_stop_sprite_menu",
|
|
21
22
|
"sensing_of_object_menu",
|
|
23
|
+
"sensing_fulltouchingobjectmenu",
|
|
24
|
+
"sensing_touchingobjectmenusprites",
|
|
25
|
+
"sensing_scrolldirections",
|
|
26
|
+
"sensing_fingeroptions",
|
|
27
|
+
"looks_changeVisibilityOfSprite_menu",
|
|
28
|
+
"looks_getOtherSpriteVisible_menu",
|
|
29
|
+
"looks_getinput_menu",
|
|
22
30
|
"pen_menu_colorParam",
|
|
23
31
|
]);
|
|
24
32
|
|
|
@@ -50,6 +58,7 @@ const LIST_OPCODE_METHOD = {
|
|
|
50
58
|
};
|
|
51
59
|
|
|
52
60
|
const SAFE_TEMP_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
61
|
+
const SAFE_VAR_REF = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
53
62
|
|
|
54
63
|
function escapeString(value) {
|
|
55
64
|
return String(value)
|
|
@@ -236,6 +245,12 @@ class Decompiler {
|
|
|
236
245
|
}
|
|
237
246
|
for (const [inputName, spec] of Object.entries(entry.inputs || {})) {
|
|
238
247
|
if (spec.type === "substack") continue;
|
|
248
|
+
if (spec.type === "variable" || spec.type === "list") {
|
|
249
|
+
const field = (block.fields || {})[inputName];
|
|
250
|
+
const ref = field ? field[0] : "0";
|
|
251
|
+
args.push(SAFE_VAR_REF.test(ref) ? ref : `"${escapeString(ref)}"`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
239
254
|
args.push(this.renderExpression(block.inputs[inputName]));
|
|
240
255
|
}
|
|
241
256
|
return `${name}(${args.join(", ")})`;
|
|
@@ -396,6 +411,18 @@ class Decompiler {
|
|
|
396
411
|
option === "timer" ? "timerGreaterThan" : "loudnessGreaterThan";
|
|
397
412
|
return `${pad}when ${kind}(${this.renderExpression(block.inputs.VALUE)}) {\n${body(block.next)}\n}`;
|
|
398
413
|
}
|
|
414
|
+
case "event_always":
|
|
415
|
+
return `${pad}when always {\n${body(block.next)}\n}`;
|
|
416
|
+
case "event_whenanything":
|
|
417
|
+
return `${pad}when whenAnything(${this.renderExpression(block.inputs.ANYTHING)}) {\n${body(block.next)}\n}`;
|
|
418
|
+
case "event_whenjavascript":
|
|
419
|
+
return `${pad}when whenJavaScript(${this.renderExpression(block.inputs.JS)}) {\n${body(block.next)}\n}`;
|
|
420
|
+
case "event_whenkeyhit":
|
|
421
|
+
return `${pad}when whenKeyHit("${escapeString((block.fields.KEY_OPTION || ["space"])[0])}") {\n${body(block.next)}\n}`;
|
|
422
|
+
case "event_whenmousescrolled":
|
|
423
|
+
return `${pad}when whenMouseScrolled("${escapeString((block.fields.KEY_OPTION || ["up"])[0])}") {\n${body(block.next)}\n}`;
|
|
424
|
+
case "event_whenstopclicked":
|
|
425
|
+
return `${pad}when whenStopClicked {\n${body(block.next)}\n}`;
|
|
399
426
|
default:
|
|
400
427
|
return this.renderExtensionHat(block, pad, body);
|
|
401
428
|
}
|
package/src/vm.js
CHANGED
|
@@ -7,13 +7,26 @@
|
|
|
7
7
|
* requires the VM lazily. Loading a project never draws anything, so the
|
|
8
8
|
* no-op renderer is enough to deserialize targets, costumes, and blocks.
|
|
9
9
|
*
|
|
10
|
-
* The VM is
|
|
11
|
-
*
|
|
10
|
+
* The VM is vendored as a git submodule and built into `vendor/pm-vm-dist` by
|
|
11
|
+
* `scripts/build-vm.js`, much like the PenguinMod Packager. `fx test` reports
|
|
12
|
+
* a clear error when that build is missing instead of failing at import time.
|
|
12
13
|
*/
|
|
13
14
|
import crypto from "node:crypto";
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
14
17
|
import { createRequire } from "node:module";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
15
19
|
import { runInThisContext } from "node:vm";
|
|
16
20
|
|
|
21
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
22
|
+
const VM_BUNDLE = path.join(
|
|
23
|
+
ROOT,
|
|
24
|
+
"vendor",
|
|
25
|
+
"pm-vm-dist",
|
|
26
|
+
"dist",
|
|
27
|
+
"scratch-vm.js",
|
|
28
|
+
);
|
|
29
|
+
|
|
17
30
|
let modules = null;
|
|
18
31
|
|
|
19
32
|
/** Blob URLs the VM created for project extension sources, keyed by URL. */
|
|
@@ -108,26 +121,41 @@ function installGlobals() {
|
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
/**
|
|
111
|
-
* Load
|
|
112
|
-
* @returns {{
|
|
124
|
+
* Load the vendored PenguinMod VM on first use.
|
|
125
|
+
* @returns {{
|
|
126
|
+
* VirtualMachine: any,
|
|
127
|
+
* centralDispatch: any,
|
|
128
|
+
* extensionApi: any,
|
|
129
|
+
* Cast: any,
|
|
130
|
+
* Color: any,
|
|
131
|
+
* }}
|
|
113
132
|
*/
|
|
114
133
|
export function loadVmModules() {
|
|
115
134
|
if (modules) return modules;
|
|
116
135
|
installGlobals();
|
|
117
136
|
const require = createRequire(import.meta.url);
|
|
118
|
-
let
|
|
119
|
-
let centralDispatch;
|
|
137
|
+
let api;
|
|
120
138
|
try {
|
|
121
|
-
|
|
122
|
-
centralDispatch = require("scratch-vm/src/dispatch/central-dispatch");
|
|
139
|
+
api = require(VM_BUNDLE);
|
|
123
140
|
} catch (error) {
|
|
141
|
+
if (fs.existsSync(VM_BUNDLE)) throw error;
|
|
124
142
|
throw new Error(
|
|
125
|
-
"Running tests needs the PenguinMod VM, which
|
|
126
|
-
"
|
|
143
|
+
"Running tests needs the PenguinMod VM build, which is missing. " +
|
|
144
|
+
"Run `npm run build:vm` from the Triflare Fx checkout (or reinstall " +
|
|
145
|
+
"the package).",
|
|
127
146
|
{ cause: error },
|
|
128
147
|
);
|
|
129
148
|
}
|
|
130
|
-
|
|
149
|
+
if (
|
|
150
|
+
typeof api?.VirtualMachine !== "function" ||
|
|
151
|
+
!api.centralDispatch ||
|
|
152
|
+
!api.extensionApi
|
|
153
|
+
) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`The PenguinMod VM at ${VM_BUNDLE} did not export the expected API.`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
modules = api;
|
|
131
159
|
return modules;
|
|
132
160
|
}
|
|
133
161
|
|