diff --git a/Cargo.lock b/Cargo.lock index 3601bee..7005717 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,63 +3,85 @@ version = 3 [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "annotate-snippets" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e35ed54e5ea7997c14ed4c70ba043478db1112e98263b3b035907aa197d991" +dependencies = [ + "anstyle", + "unicode-width", +] + [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -67,24 +89,33 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" + +[[package]] +name = "arbitrary" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "ascii-canvas" -version = "3.0.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" dependencies = [ "term", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "beef" @@ -94,30 +125,24 @@ checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -128,11 +153,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "cfg_aliases", +] + [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -140,17 +174,65 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e804ac3194a48bb129643eb1d62fcc20d18c6b8c181704489353d13120bcd1" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" -version = "4.5.3" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -158,9 +240,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -170,40 +252,29 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.1" +version = "4.5.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb" dependencies = [ "clap", ] [[package]] name = "clap_complete_command" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d" +checksum = "da8e198c052315686d36371e8a3c5778b7852fc75cc313e4e11eeb7a644a1b62" dependencies = [ "clap", "clap_complete", - "clap_complete_fig", "clap_complete_nushell", ] -[[package]] -name = "clap_complete_fig" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b3e65f91fabdd23cac3d57d39d5d938b4daabd070c335c006dccb866a61110" -dependencies = [ - "clap", - "clap_complete", -] - [[package]] name = "clap_complete_nushell" -version = "0.1.11" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d02bc8b1a18ee47c4d2eec3fb5ac034dc68ebea6125b1509e9ccdffcddce66e" +checksum = "315902e790cc6e5ddd20cbd313c1d0d49db77f191e149f96397230fb82a17677" dependencies = [ "clap", "clap_complete", @@ -211,9 +282,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.3" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -223,15 +294,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" @@ -244,25 +315,49 @@ dependencies = [ ] [[package]] -name = "crc32fast" -version = "1.4.0" +name = "constant_time_eq" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ - "cfg-if", + "libc", ] [[package]] -name = "crossbeam-utils" -version = "0.8.19" +name = "crc" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] [[package]] -name = "crunchy" -version = "0.2.2" +name = "crc-catalog" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -276,13 +371,39 @@ dependencies = [ [[package]] name = "csscolorparser" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +checksum = "46f9a16a848a7fb95dd47ce387ac1ee9a6df879ba784b815537fcd388a1a8288" dependencies = [ "phf", ] +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -291,42 +412,46 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "cfg-if", - "dirs-sys-next", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "either" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] -name = "either" -version = "1.10.0" +name = "ena" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] [[package]] -name = "ena" -version = "0.14.2" +name = "env_logger" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ + "humantime", + "is-terminal", "log", + "regex", + "termcolor", ] [[package]] @@ -343,9 +468,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -357,15 +482,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "fuzzy-matcher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" -dependencies = [ - "thread_local", -] - [[package]] name = "fxhash" version = "0.2.1" @@ -387,9 +503,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -398,21 +514,22 @@ dependencies = [ [[package]] name = "goboscript" -version = "1.0.0" +version = "1.1.0" dependencies = [ + "annotate-snippets", "anyhow", - "bumpalo", "clap", "clap_complete_command", "clap_derive", "colored", "csscolorparser", - "fuzzy-matcher", "fxhash", "lalrpop", "lalrpop-util", + "log", "logos", "md-5", + "pretty_env_logger", "serde", "serde_json", "smol_str", @@ -422,9 +539,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" @@ -432,36 +549,110 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "indexmap" -version = "2.2.5" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] [[package]] name = "lalrpop" -version = "0.20.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +checksum = "06093b57658c723a21da679530e061a8c25340fa5a6f98e313b542268c7e2a1f" dependencies = [ "ascii-canvas", "bit-set", @@ -472,75 +663,71 @@ dependencies = [ "pico-args", "regex", "regex-syntax", + "sha3", "string_cache", "term", - "tiny-keccak", "unicode-xid", "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.20.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +checksum = "feee752d43abd0f4807a921958ab4131f692a44d4d599733d4419c5d586176ce" dependencies = [ "regex-automata", + "rustversion", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "libredox" -version = "0.0.1" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" -dependencies = [ - "bitflags 2.4.2", - "libc", - "redox_syscall", -] +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "logos" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161971eb88a0da7ae0c333e1063467c5b5727e7fb6b710b8db4814eade3a42e8" +checksum = "1c6b6e02facda28ca5fb8dbe4b152496ba3b1bd5a4b40bb2b1b2d8ad74e0f39b" dependencies = [ "logos-derive", ] [[package]] name = "logos-codegen" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e31badd9de5131fdf4921f6473d457e3dd85b11b7f091ceb50e4df7c3eeb12a" +checksum = "b32eb6b5f26efacd015b000bfc562186472cd9b34bdba3f6b264e2a052676d10" dependencies = [ "beef", "fnv", @@ -553,13 +740,23 @@ dependencies = [ [[package]] name = "logos-derive" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c2a69b3eb68d5bd595107c9ee58d7e07fe2bb5e360cc85b0f084dedac80de0a" +checksum = "3e5d0c5463c911ef55624739fc353238b4e310f0144be1f875dc42fec6bfd5ec" dependencies = [ "logos-codegen", ] +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "md-5" version = "0.10.6" @@ -572,36 +769,42 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "new_debug_unreachable" -version = "1.0.4" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -609,22 +812,32 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", ] [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", @@ -687,26 +900,57 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -717,40 +961,44 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "bitflags 1.3.2", + "getrandom", ] [[package]] -name = "redox_users" -version = "0.4.4" +name = "redox_syscall" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "getrandom", - "libredox", - "thiserror", + "bitflags", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -760,9 +1008,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -771,21 +1019,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -804,18 +1052,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -824,24 +1072,58 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.11" @@ -850,16 +1132,17 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smol_str" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49" +checksum = "66eaf762c5af19db3108300515c8aa7a50efc90ff745f4c62288052ebf9fdd25" dependencies = [ + "borsh", "serde", ] @@ -878,15 +1161,21 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.52" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -895,29 +1184,37 @@ dependencies = [ [[package]] name = "term" -version = "0.7.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +checksum = "4df4175de05129f31b80458c6df371a15e7fc3fd367272e6bf938e5c351c7ea0" dependencies = [ - "dirs-next", - "rustversion", - "winapi", + "home", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -925,29 +1222,29 @@ dependencies = [ ] [[package]] -name = "thread_local" -version = "1.1.8" +name = "time" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ - "cfg-if", - "once_cell", + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", ] [[package]] -name = "tiny-keccak" -version = "2.0.2" +name = "time-core" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "toml" -version = "0.8.11" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -957,18 +1254,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.7" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", @@ -985,27 +1282,33 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -1023,37 +1326,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.48.0" @@ -1069,7 +1350,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -1089,17 +1379,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1110,9 +1401,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -1122,9 +1413,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -1134,9 +1425,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -1146,9 +1443,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -1158,9 +1455,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -1170,9 +1467,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -1182,27 +1479,127 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] [[package]] -name = "zip" -version = "0.6.6" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", "crc32fast", "crossbeam-utils", + "deflate64", + "displaydoc", "flate2", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "rand", + "sha1", + "thiserror", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 7e55962..b17ca04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,48 +1,27 @@ [package] name = "goboscript" -version = "1.0.0" -description = "goboscript is the Scratch compiler." -license = "MIT" -keywords = ["scratch", "compiler"] -categories = ["compilers"] -documentation = "https://aspizu.github.io/goboscript" -homepage = "https://github.com/aspizu/goboscript" -repository = "https://github.com/aspizu/goboscript" +version = "1.1.0" edition = "2021" -exclude = [ - "/tools", - "/docs", - "/tests", - "/editors", - "/examples", - "gdsl.py", - "gdsl.txt", - "mkdocs.yml", -] [dependencies] -anyhow = "1.0.81" -bumpalo = "3.15.4" -clap = { version = "4.5.3", features = ["derive"] } -clap_complete_command = "0.5.1" -clap_derive = "4.5.3" -colored = "2.1.0" -csscolorparser = "0.6.2" -fuzzy-matcher = "0.3.7" -fxhash = "0.2.1" -lalrpop-util = "0.20.2" -logos = "0.14.0" -md-5 = "0.10.6" -serde = { version = "1.0.197", features = ["derive"] } -serde_json = "1.0.114" -smol_str = "0.2.1" -toml = "0.8.11" -zip = { version = "0.6.6", default-features = false, features = ["deflate"] } +annotate-snippets = "0.11.4" +anyhow = "1.0.91" +clap = { version = "4.5.20", features = ["derive"] } +clap_complete_command = "0.6.1" +clap_derive = "4.5.18" +colored = "2.1.0" +csscolorparser = "0.7.0" +fxhash = "0.2.1" +lalrpop-util = "0.22.0" +log = "0.4.22" +logos = "0.14.2" +md-5 = "0.10.6" +pretty_env_logger = "0.5.0" +serde = { version = "1.0.210", features = ["derive"] } +serde_json = "1.0.128" +smol_str = "0.3.1" +toml = "0.8.19" +zip = { version = "2.2.0", features = ["deflate"] } [build-dependencies] -lalrpop = "0.20.2" - -[profile.release] -panic = "abort" -lto = "thin" -debug = true +lalrpop = "0.22.0" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..1590630 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +too-many-arguments-threshold = 10 diff --git a/editors/code/syntaxes/goboscript.tmGrammar.yml b/editors/code/syntaxes/goboscript.tmGrammar.yml index 90677f1..2bfa4a1 100644 --- a/editors/code/syntaxes/goboscript.tmGrammar.yml +++ b/editors/code/syntaxes/goboscript.tmGrammar.yml @@ -5,6 +5,9 @@ patterns: - name: comment.block begin: "/\\*" end: "\\*/" + - name: comment.block + begin: "```" + end: "```" - name: comment.line begin: "#" end: "$" @@ -23,9 +26,9 @@ patterns: - name: punctuation match: ",|;" - name: keyword - match: "\\b(costumes|sounds|global|variables|lists|nowarp|onflag|onkey|onbackdrop|onloudness|ontimer|on|onclone)\\b" + match: "\\b(costumes|sounds|global|list|nowarp|onflag|onkey|onbackdrop|onloudness|ontimer|on|onclone)\\b" - name: keyword.control - match: "\\b(if|else|elif|until|forever|repeat|delete|at|add|to|insert|true|false)\\b" + match: "\\b(if|else|elif|until|forever|repeat|delete|at|add|to|insert|true|false|as)\\b" - name: keyword match: "\\b(error|warn|breakpoint|local|not|and|or|in|length|round|abs|floor|ceil|sqrt|sin|cos|tan|asin|acos|atan|ln|log|antiln|antilog)\\b" - name: support.function.builtin @@ -42,13 +45,8 @@ patterns: match: "[a-zA-Z_0-9]+!" - name: constant.numeric match: "\\b([0-9][_0-9]*|0x[_0-9a-fA-F]+|0b[_0-1]+|0o[_0-7]+|([0-9][0-9]*)?\\.[0-9][_0-9]*)\\b" - - name: punctuation - match: "([a-zA-Z_0-9][_a-zA-Z_0-9]*)\\.([a-zA-Z_0-9][_a-zA-Z_0-9]*)" - captures: - 1: - name: entity.name.type - 2: - name: entity.name.function + - name: entity.name.function + match: "\\.([a-zA-Z_0-9][_a-zA-Z_0-9]*)" - begin: "(enum)\\s+([a-zA-Z_][_a-zA-Z0-9]*)" end: "\\{" captures: diff --git a/rustfmt.toml b/rustfmt.toml index b258fb6..cef2f1c 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,4 @@ -use_small_heuristics = "Max" -max_width = 88 -imports_granularity = "Crate" -group_imports = "StdExternalCrate" +imports_granularity = "Crate" +group_imports = "StdExternalCrate" match_arm_leading_pipes = "Preserve" -where_single_line = true +where_single_line = true diff --git a/src/ast.rs b/src/ast.rs index 95a1211..f722018 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,399 +1,39 @@ -use std::{cell::RefCell, path::Path, rc::Rc}; - -use fxhash::{FxHashMap, FxHashSet}; -use logos::Span; -use serde::Serialize; -use smol_str::SmolStr; - -use crate::blocks::{BinOp, Block, Repr, UnOp}; - -pub type Rrc = Rc>; - -#[derive(Debug, Default)] -pub struct Project { - pub stage: Sprite, - pub sprites: FxHashMap, -} - -impl Project { - pub fn new(stage: Sprite, mut sprites: FxHashMap) -> Self { - for sprite in sprites.values_mut() { - sprite.vars.retain(|name, _| !stage.vars.contains_key(name)); - } - Self { stage, sprites } - } -} - -#[derive(Debug, Default)] -pub struct Sprite { - pub costumes: Vec, - pub procs: FxHashMap, - pub used_procs: FxHashSet, - pub enums: FxHashMap, - pub vars: FxHashMap, - pub lists: FxHashMap, - pub broadcasts: FxHashSet, - pub on_messages: FxHashMap, - pub events: Vec, -} - -#[derive(Debug)] -pub struct Costume { - pub name: SmolStr, - pub path: SmolStr, - pub span: Span, -} - -impl Costume { - pub fn new(path: SmolStr, span: Span, alias: Option) -> Self { - // TODO: validate file extension - let name = alias.unwrap_or_else(|| { - Path::new(path.as_str()).file_stem().unwrap().to_str().unwrap().into() - }); - Self { name, path, span } - } -} - -#[derive(Debug)] -pub struct Enum { - pub name: SmolStr, - pub span: Span, - pub variants: Vec<(SmolStr, Span)>, - pub used_variants: FxHashSet, -} - -impl Enum { - pub fn new(name: SmolStr, span: Span, variants: Vec<(SmolStr, Span)>) -> Self { - Self { name, span, variants, used_variants: Default::default() } - } -} - -#[derive(Debug)] -pub struct Var { - pub name: SmolStr, - pub span: Span, - pub default: Literal, - pub used: bool, -} - -impl Var { - pub fn new(name: SmolStr, span: Span, default: Option) -> Self { - Self { name, span, default: default.unwrap_or(Literal::Int(0)), used: false } - } -} - -#[derive(Debug)] -pub struct List { - pub name: SmolStr, - pub span: Span, - pub default: Literals, - pub used: bool, -} - -impl List { - pub fn new(name: SmolStr, span: Span, default: Literals) -> Self { - Self { name, span, default, used: false } - } -} - -#[derive(Debug, Default)] -pub struct References { - pub procs: FxHashSet, - pub vars: FxHashSet, - pub lists: FxHashSet, - pub messages: FxHashSet, - pub enum_variants: FxHashSet<(SmolStr, SmolStr)>, -} - -#[derive(Debug)] -pub struct Proc { - pub name: SmolStr, - pub span: Span, - pub args: Vec<(SmolStr, Span)>, - pub used_args: FxHashMap, - pub locals: FxHashMap, - pub body: Stmts, - pub warp: bool, - pub references: References, -} - -impl Proc { - pub fn new( - name: SmolStr, - span: Span, - args: Vec<(SmolStr, Span)>, - body: Stmts, - warp: bool, - ) -> Self { - let used_args = args.iter().map(|(name, _)| (name.clone(), false)).collect(); - Self { - name, - span, - args, - used_args, - body, - warp, - references: Default::default(), - locals: Default::default(), - } - } -} - -#[derive(Debug)] -pub struct OnMessage { - pub message: SmolStr, - pub span: Span, - pub body: Stmts, - pub used: bool, - pub references: References, -} - -impl OnMessage { - pub fn new(message: SmolStr, span: Span, body: Stmts) -> Self { - Self { message, span, body, used: false, references: Default::default() } - } -} - -#[derive(Debug)] -pub struct Event { - pub kind: EventDetail, - pub span: Span, - pub body: Stmts, - pub references: References, -} - -#[derive(Debug)] -pub enum EventDetail { - OnFlag, - OnKey { key: SmolStr, span: Span }, - OnClick, - OnBackdrop { backdrop: SmolStr, span: Span }, - OnLoudnessGt { value: Rrc }, - OnTimerGt { value: Rrc }, - OnClone, -} - -impl EventDetail { - pub fn to_event(self, span: Span, body: Stmts) -> Event { - Event { kind: self, span, body, references: Default::default() } - } -} - -pub type Stmts = Vec; - -#[derive(Debug)] -pub enum Stmt { - Repeat { - times: Rrc, - body: Stmts, - }, - Forever { - body: Stmts, - span: Span, - }, - Branch { - cond: Rrc, - if_body: Stmts, - else_body: Stmts, - }, - Until { - cond: Rrc, - body: Stmts, - }, - SetVar { - name: SmolStr, - span: Span, - value: Rrc, - is_local: bool, - }, - ChangeVar { - name: SmolStr, - span: Span, - value: Rrc, - }, - Show { - name: SmolStr, - span: Span, - }, - Hide { - name: SmolStr, - span: Span, - }, - ListAdd { - name: SmolStr, - span: Span, - value: Rrc, - }, - ListDelete { - name: SmolStr, - span: Span, - index: Rrc, - }, - ListDeleteAll { - name: SmolStr, - span: Span, - }, - ListInsert { - name: SmolStr, - span: Span, - index: Rrc, - value: Rrc, - }, - ListSet { - name: SmolStr, - span: Span, - index: Rrc, - value: Rrc, - }, - ListChange { - op: BinOp, - name: SmolStr, - span: Span, - index: Rrc, - value: Rrc, - }, - Block { - block: Block, - span: Span, - args: Exprs, - }, - ProcCall { - name: SmolStr, - span: Span, - args: Exprs, - }, -} - -pub type Exprs = Vec>; - -#[derive(Debug, Clone)] -pub enum Expr { - Int(i64), - Float(f64), - Str(SmolStr), - Name { - name: SmolStr, - span: Span, - }, - Arg { - name: SmolStr, - span: Span, - }, - Repr { - repr: Repr, - span: Span, - args: Exprs, - }, - UnOp { - op: UnOp, - val: Rrc, - }, - BinOp { - op: BinOp, - lhs: Rrc, - rhs: Rrc, - }, - EnumVariant { - enum_name: SmolStr, - enum_span: Span, - variant_name: SmolStr, - variant_span: Span, - }, -} - -impl Expr { - pub fn as_int(&self) -> Option { - match self { - Expr::Int(value) => Some(*value), - Expr::Float(value) => Some(*value as i64), - _ => None, - } - } - - pub fn is_zero(&self) -> bool { - self.as_int().is_some_and(|it| it == 0) - } - - pub fn try_to_string(&self) -> Option { - match self { - Expr::Int(value) => Some(value.to_string()), - Expr::Float(value) => Some(value.to_string()), - Expr::Str(value) => Some(value.to_string()), - _ => None, - } - } -} - -impl From for Rc> { - fn from(val: Expr) -> Self { - Rc::new(RefCell::new(val)) - } -} - -pub type Literals = Vec; - -#[derive(Debug)] -pub enum Literal { - Int(i64), - Float(f64), - Str(SmolStr), -} - -impl Serialize for Literal { - fn serialize(&self, serializer: S) -> Result - where S: serde::Serializer { - match self { - Literal::Int(value) => serializer.serialize_i64(*value), - Literal::Float(value) => serializer.serialize_f64(*value), - Literal::Str(value) => serializer.serialize_str(value), - } - } -} - -impl UnOp { - pub fn to_expr(self, val: Rrc) -> Expr { - Expr::UnOp { op: self, val } - } -} - -impl BinOp { - pub fn to_expr(self, lhs: Rrc, rhs: Rrc) -> Expr { - Expr::BinOp { op: self, lhs, rhs } - } -} - -impl Event { - pub fn opcode(&self) -> &'static str { - match &self.kind { - EventDetail::OnFlag => "event_whenflagclicked", - EventDetail::OnKey { .. } => "event_whenkeypressed", - EventDetail::OnClick => "event_whenthisspriteclicked", - EventDetail::OnBackdrop { .. } => "event_whenbackdropswitchesto", - EventDetail::OnLoudnessGt { .. } | EventDetail::OnTimerGt { .. } => { - "event_whengreaterthan" - } - EventDetail::OnClone => "control_start_as_clone", - } - } -} - -impl Stmt { - pub fn span(&self) -> &Span { - match self { - Stmt::Forever { span, .. } => span, - Stmt::SetVar { span, .. } => span, - Stmt::ChangeVar { span, .. } => span, - Stmt::Show { span, .. } => span, - Stmt::Hide { span, .. } => span, - Stmt::ListAdd { span, .. } => span, - Stmt::ListDelete { span, .. } => span, - Stmt::ListDeleteAll { span, .. } => span, - Stmt::ListInsert { span, .. } => span, - Stmt::ListSet { span, .. } => span, - Stmt::ListChange { span, .. } => span, - Stmt::Block { span, .. } => span, - Stmt::ProcCall { span, .. } => span, - _ => unreachable!(), - } - } -} +mod arg; +mod costume; +mod enum_; +mod enum_variant; +mod event; +mod event_kind; +mod expr; +mod list; +mod name; +mod proc; +mod project; +mod sprite; +mod stmt; +mod struct_; +mod struct_field; +mod struct_literal_field; +mod type_; +mod value; +mod var; + +pub use arg::*; +pub use costume::*; +pub use enum_::*; +pub use enum_variant::*; +pub use event::*; +pub use event_kind::*; +pub use expr::*; +pub use list::*; +pub use name::*; +pub use proc::*; +pub use project::*; +pub use sprite::*; +pub use stmt::*; +pub use struct_::*; +pub use struct_field::*; +pub use struct_literal_field::*; +pub use type_::*; +pub use value::*; +pub use var::*; diff --git a/src/ast/arg.rs b/src/ast/arg.rs new file mode 100644 index 0000000..18f5520 --- /dev/null +++ b/src/ast/arg.rs @@ -0,0 +1,11 @@ +use logos::Span; +use smol_str::SmolStr; + +use super::type_::Type; + +#[derive(Debug)] +pub struct Arg { + pub name: SmolStr, + pub span: Span, + pub type_: Type, +} diff --git a/src/ast/costume.rs b/src/ast/costume.rs new file mode 100644 index 0000000..74d2454 --- /dev/null +++ b/src/ast/costume.rs @@ -0,0 +1,25 @@ +use std::path::Path; + +use logos::Span; +use smol_str::SmolStr; + +#[derive(Debug)] +pub struct Costume { + pub name: SmolStr, + pub path: SmolStr, + pub span: Span, +} + +impl Costume { + pub fn new(path: SmolStr, alias: Option, span: Span) -> Self { + let name = alias.unwrap_or_else(|| { + Path::new(&path) + .file_stem() + .unwrap() + .to_str() + .unwrap() + .into() + }); + Self { name, path, span } + } +} diff --git a/src/ast/enum_.rs b/src/ast/enum_.rs new file mode 100644 index 0000000..71b3402 --- /dev/null +++ b/src/ast/enum_.rs @@ -0,0 +1,11 @@ +use logos::Span; +use smol_str::SmolStr; + +use super::enum_variant::EnumVariant; + +#[derive(Debug)] +pub struct Enum { + pub name: SmolStr, + pub span: Span, + pub variants: Vec, +} diff --git a/src/ast/enum_variant.rs b/src/ast/enum_variant.rs new file mode 100644 index 0000000..236ed8e --- /dev/null +++ b/src/ast/enum_variant.rs @@ -0,0 +1,11 @@ +use logos::Span; +use smol_str::SmolStr; + +use super::value::Value; + +#[derive(Debug)] +pub struct EnumVariant { + pub name: SmolStr, + pub span: Span, + pub value: Value, +} diff --git a/src/ast/event.rs b/src/ast/event.rs new file mode 100644 index 0000000..6d66612 --- /dev/null +++ b/src/ast/event.rs @@ -0,0 +1,10 @@ +use logos::Span; + +use super::{event_kind::EventKind, stmt::Stmt}; + +#[derive(Debug)] +pub struct Event { + pub kind: EventKind, + pub span: Span, + pub body: Vec, +} diff --git a/src/ast/event_kind.rs b/src/ast/event_kind.rs new file mode 100644 index 0000000..e5c9d95 --- /dev/null +++ b/src/ast/event_kind.rs @@ -0,0 +1,39 @@ +use logos::Span; +use smol_str::SmolStr; + +use super::{expr::Expr, Event, Stmt}; +use crate::misc::Rrc; + +#[allow(clippy::enum_variant_names)] +#[derive(Debug)] +pub enum EventKind { + OnFlag, + OnKey { key: SmolStr, span: Span }, + OnClick, + OnBackdrop { backdrop: SmolStr, span: Span }, + OnLoudnessGt { value: Rrc }, + OnTimerGt { value: Rrc }, + OnClone, +} + +impl EventKind { + pub fn opcode(&self) -> &'static str { + match &self { + EventKind::OnFlag => "event_whenflagclicked", + EventKind::OnKey { .. } => "event_whenkeypressed", + EventKind::OnClick => "event_whenthisspriteclicked", + EventKind::OnBackdrop { .. } => "event_whenbackdropswitchesto", + EventKind::OnLoudnessGt { .. } | EventKind::OnTimerGt { .. } => "event_whengreaterthan", + EventKind::OnClone => "control_start_as_clone", + } + } + + #[allow(clippy::wrong_self_convention)] + pub fn to_event(self, span: Span, body: Vec) -> Event { + Event { + kind: self, + body, + span, + } + } +} diff --git a/src/ast/expr.rs b/src/ast/expr.rs new file mode 100644 index 0000000..e17be70 --- /dev/null +++ b/src/ast/expr.rs @@ -0,0 +1,88 @@ +use std::cell::RefCell; + +use logos::Span; +use smol_str::SmolStr; + +use super::{value::Value, Name, StructLiteralField}; +use crate::{ + blocks::{BinOp, Repr, UnOp}, + misc::Rrc, +}; + +#[derive(Debug)] +pub enum Expr { + Value { + value: Value, + span: Span, + }, + Name(Name), + Dot { + lhs: Rrc, + rhs: SmolStr, + rhs_span: Span, + }, + Arg(Name), + Repr { + repr: Repr, + span: Span, + args: Vec>, + }, + UnOp { + op: UnOp, + span: Span, + opr: Rrc, + }, + BinOp { + op: BinOp, + span: Span, + lhs: Rrc, + rhs: Rrc, + }, + StructLiteral { + name: SmolStr, + span: Span, + fields: Vec, + }, +} + +impl Expr { + pub fn span(&self) -> Span { + match self { + Self::Value { span, .. } => span.clone(), + Self::Name(name) => name.span(), + Self::Dot { lhs, rhs_span, .. } => lhs.borrow().span().start..rhs_span.end, + Self::Arg(name) => name.span(), + Self::Repr { span, .. } => span.clone(), + Self::UnOp { span, .. } => span.clone(), + Self::BinOp { span, .. } => span.clone(), + Self::StructLiteral { span, .. } => span.clone(), + } + } +} + +impl UnOp { + pub fn to_expr(self, span: Span, expr: Rrc) -> Expr { + Expr::UnOp { + op: self, + span, + opr: expr, + } + } +} + +impl BinOp { + pub fn to_expr(self, span: Span, lhs: Rrc, rhs: Rrc) -> Expr { + Expr::BinOp { + op: self, + span, + lhs, + rhs, + } + } +} + +impl From for Rrc { + fn from(value: Expr) -> Self { + RefCell::new(value).into() + } +} diff --git a/src/ast/list.rs b/src/ast/list.rs new file mode 100644 index 0000000..b3f85ab --- /dev/null +++ b/src/ast/list.rs @@ -0,0 +1,18 @@ +use logos::Span; +use smol_str::SmolStr; + +use super::type_::Type; + +#[derive(Debug)] +pub struct List { + pub name: SmolStr, + pub span: Span, + pub type_: Type, + pub cmd: Option, +} + +#[derive(Debug)] +pub struct Cmd { + pub cmd: SmolStr, + pub span: Span, +} diff --git a/src/ast/name.rs b/src/ast/name.rs new file mode 100644 index 0000000..6ef6197 --- /dev/null +++ b/src/ast/name.rs @@ -0,0 +1,55 @@ +use logos::Span; +use smol_str::SmolStr; + +#[derive(Debug, Clone)] +pub enum Name { + Name { + name: SmolStr, + span: Span, + }, + DotName { + lhs: SmolStr, + lhs_span: Span, + rhs: SmolStr, + rhs_span: Span, + }, +} + +impl Name { + pub fn span(&self) -> Span { + match self { + Self::Name { span, .. } => span.clone(), + Self::DotName { + lhs_span, rhs_span, .. + } => lhs_span.start..rhs_span.end, + } + } + + pub fn basename(&self) -> &SmolStr { + match self { + Self::Name { name, .. } => name, + Self::DotName { lhs, .. } => lhs, + } + } + + pub fn basespan(&self) -> Span { + match self { + Self::Name { span, .. } => span.clone(), + Self::DotName { lhs_span, .. } => lhs_span.clone(), + } + } + + pub fn fieldname(&self) -> Option<&SmolStr> { + match self { + Self::Name { .. } => None, + Self::DotName { rhs, .. } => Some(rhs), + } + } + + pub fn fieldspan(&self) -> Span { + match self { + Self::Name { span, .. } => span.clone(), + Self::DotName { rhs_span, .. } => rhs_span.clone(), + } + } +} diff --git a/src/ast/proc.rs b/src/ast/proc.rs new file mode 100644 index 0000000..6efd703 --- /dev/null +++ b/src/ast/proc.rs @@ -0,0 +1,28 @@ +use fxhash::FxHashMap; +use logos::Span; +use smol_str::SmolStr; + +use super::{arg::Arg, stmt::Stmt, var::Var}; + +#[derive(Debug)] +pub struct Proc { + pub name: SmolStr, + pub span: Span, + pub args: Vec, + pub locals: FxHashMap, + pub body: Vec, + pub warp: bool, +} + +impl Proc { + pub fn new(name: SmolStr, span: Span, args: Vec, body: Vec, warp: bool) -> Self { + Self { + name, + span, + args, + locals: FxHashMap::default(), + body, + warp, + } + } +} diff --git a/src/ast/project.rs b/src/ast/project.rs new file mode 100644 index 0000000..f5c6db1 --- /dev/null +++ b/src/ast/project.rs @@ -0,0 +1,10 @@ +use fxhash::FxHashMap; +use smol_str::SmolStr; + +use super::sprite::Sprite; + +#[derive(Debug)] +pub struct Project { + pub stage: Sprite, + pub sprites: FxHashMap, +} diff --git a/src/ast/sprite.rs b/src/ast/sprite.rs new file mode 100644 index 0000000..c2b85b2 --- /dev/null +++ b/src/ast/sprite.rs @@ -0,0 +1,17 @@ +use fxhash::FxHashMap; +use smol_str::SmolStr; + +use super::{ + costume::Costume, enum_::Enum, event::Event, list::List, proc::Proc, struct_::Struct, var::Var, +}; + +#[derive(Debug, Default)] +pub struct Sprite { + pub costumes: Vec, + pub procs: FxHashMap, + pub enums: FxHashMap, + pub structs: FxHashMap, + pub vars: FxHashMap, + pub lists: FxHashMap, + pub events: Vec, +} diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs new file mode 100644 index 0000000..fb53b36 --- /dev/null +++ b/src/ast/stmt.rs @@ -0,0 +1,69 @@ +use logos::Span; +use smol_str::SmolStr; + +use super::{expr::Expr, type_::Type, Name}; +use crate::{blocks::Block, misc::Rrc}; + +#[derive(Debug)] +pub enum Stmt { + Repeat { + times: Rrc, + body: Vec, + }, + Forever { + body: Vec, + span: Span, + }, + Branch { + cond: Rrc, + if_body: Vec, + else_body: Vec, + }, + Until { + cond: Rrc, + body: Vec, + }, + SetVar { + name: Name, + value: Rrc, + type_: Type, + is_local: bool, + }, + Show(Name), + Hide(Name), + AddToList { + name: Name, + value: Rrc, + }, + DeleteList(Name), + DeleteListIndex { + name: Name, + index: Rrc, + }, + InsertAtList { + name: Name, + index: Rrc, + value: Rrc, + }, + SetListIndex { + name: Name, + index: Rrc, + value: Rrc, + }, + Block { + block: Block, + span: Span, + args: Vec>, + }, + ProcCall { + name: SmolStr, + span: Span, + args: Vec>, + }, +} + +impl Stmt { + pub fn span(&self) -> &Span { + todo!() + } +} diff --git a/src/ast/struct_.rs b/src/ast/struct_.rs new file mode 100644 index 0000000..69e69a8 --- /dev/null +++ b/src/ast/struct_.rs @@ -0,0 +1,24 @@ +use logos::Span; +use smol_str::SmolStr; + +use super::struct_field::StructField; + +#[derive(Debug)] +pub struct Struct { + pub name: SmolStr, + pub span: Span, + pub fields: Vec, +} + +impl Struct { + pub fn new(name: SmolStr, span: Span, fields: Vec<(SmolStr, Span)>) -> Self { + Self { + name, + span, + fields: fields + .into_iter() + .map(|(name, span)| StructField { name, span }) + .collect(), + } + } +} diff --git a/src/ast/struct_field.rs b/src/ast/struct_field.rs new file mode 100644 index 0000000..f07a38d --- /dev/null +++ b/src/ast/struct_field.rs @@ -0,0 +1,8 @@ +use logos::Span; +use smol_str::SmolStr; + +#[derive(Debug)] +pub struct StructField { + pub name: SmolStr, + pub span: Span, +} diff --git a/src/ast/struct_literal_field.rs b/src/ast/struct_literal_field.rs new file mode 100644 index 0000000..81fb100 --- /dev/null +++ b/src/ast/struct_literal_field.rs @@ -0,0 +1,12 @@ +use logos::Span; +use smol_str::SmolStr; + +use super::Expr; +use crate::misc::Rrc; + +#[derive(Debug)] +pub struct StructLiteralField { + pub name: SmolStr, + pub span: Span, + pub value: Rrc, +} diff --git a/src/ast/type_.rs b/src/ast/type_.rs new file mode 100644 index 0000000..3820ee2 --- /dev/null +++ b/src/ast/type_.rs @@ -0,0 +1,37 @@ +use core::fmt; +use std::fmt::Display; + +use logos::Span; +use smol_str::SmolStr; + +#[derive(Debug, Clone)] +pub enum Type { + Value, + Struct { name: SmolStr, span: Span }, +} + +impl Type { + pub fn is_value(&self) -> bool { + matches!(self, Self::Value) + } + + pub fn is_struct(&self) -> bool { + matches!(self, Self::Struct { .. }) + } + + pub fn struct_(&self) -> Option<(&SmolStr, &Span)> { + match self { + Self::Struct { name, span } => Some((name, span)), + _ => None, + } + } +} + +impl Display for Type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Type::Value => write!(f, "value"), + Type::Struct { name, span: _ } => write!(f, "{}", name), + } + } +} diff --git a/src/ast/value.rs b/src/ast/value.rs new file mode 100644 index 0000000..67838c9 --- /dev/null +++ b/src/ast/value.rs @@ -0,0 +1,80 @@ +mod binop; +mod unop; +use std::fmt::{self, Display}; + +use logos::Span; +use smol_str::SmolStr; + +use super::Expr; + +#[derive(Debug, Clone)] +pub enum Value { + Int(i64), + Float(f64), + String(SmolStr), +} + +impl Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Int(int) => write!(f, "{}", int), + Self::Float(float) => write!(f, "{}", float), + Self::String(string) => write!(f, "{}", string), + } + } +} + +impl From for Value { + fn from(int: i32) -> Self { + Self::Int(int as i64) + } +} + +impl From for Value { + fn from(int: i64) -> Self { + Self::Int(int) + } +} + +impl From for Value { + fn from(int: usize) -> Self { + Self::Int(int as i64) + } +} + +impl From for Value { + fn from(float: f64) -> Self { + Self::Float(float) + } +} + +impl From<&SmolStr> for Value { + fn from(string: &SmolStr) -> Self { + Self::String(string.clone()) + } +} + +impl From<&str> for Value { + fn from(string: &str) -> Self { + Self::String(string.into()) + } +} + +impl From for Value { + fn from(string: String) -> Self { + Self::String(string.into()) + } +} + +impl From for Value { + fn from(string: SmolStr) -> Self { + Self::String(string) + } +} + +impl Value { + #[allow(clippy::wrong_self_convention)] + pub fn to_expr(self, span: Span) -> Expr { + Expr::Value { value: self, span } + } +} diff --git a/src/ast/value/binop.rs b/src/ast/value/binop.rs new file mode 100644 index 0000000..ca889d5 --- /dev/null +++ b/src/ast/value/binop.rs @@ -0,0 +1,94 @@ +use super::Value; +use crate::blocks::BinOp; + +impl Value { + pub fn binop(&self, op: BinOp, rhs: &Value) -> Option { + match op { + BinOp::Add => self.add(rhs), + BinOp::Sub => self.sub(rhs), + BinOp::Mul => self.mul(rhs), + BinOp::Div => self.div(rhs), + BinOp::Mod => self.mod_(rhs), + BinOp::Lt => self.lt(rhs), + BinOp::Gt => self.gt(rhs), + BinOp::Eq => self.eq(rhs), + BinOp::And => self.and(rhs), + BinOp::Or => self.or(rhs), + BinOp::Join => self.join(rhs), + BinOp::In => self.in_(rhs), + BinOp::Of => self.of(rhs), + BinOp::Le => self.le(rhs), + BinOp::Ge => self.ge(rhs), + BinOp::Ne => self.ne(rhs), + BinOp::FloorDiv => self.floor_div(rhs), + } + } + + fn add(&self, rhs: &Value) -> Option { + todo!() + } + + fn sub(&self, rhs: &Value) -> Option { + todo!() + } + + fn mul(&self, rhs: &Value) -> Option { + todo!() + } + + fn div(&self, rhs: &Value) -> Option { + todo!() + } + + fn mod_(&self, rhs: &Value) -> Option { + todo!() + } + + fn lt(&self, rhs: &Value) -> Option { + todo!() + } + + fn gt(&self, rhs: &Value) -> Option { + todo!() + } + + fn eq(&self, rhs: &Value) -> Option { + todo!() + } + + fn and(&self, rhs: &Value) -> Option { + todo!() + } + + fn or(&self, rhs: &Value) -> Option { + todo!() + } + + fn join(&self, rhs: &Value) -> Option { + todo!() + } + + fn in_(&self, rhs: &Value) -> Option { + todo!() + } + + fn of(&self, rhs: &Value) -> Option { + todo!() + } + + fn le(&self, rhs: &Value) -> Option { + todo!() + } + + fn ge(&self, rhs: &Value) -> Option { + todo!() + } + + fn ne(&self, rhs: &Value) -> Option { + todo!() + } + + fn floor_div(&self, rhs: &Value) -> Option { + todo!() + } +} diff --git a/src/ast/value/unop.rs b/src/ast/value/unop.rs new file mode 100644 index 0000000..90d1cfb --- /dev/null +++ b/src/ast/value/unop.rs @@ -0,0 +1,49 @@ +use super::Value; +use crate::blocks::UnOp; + +impl Value { + pub fn unop(&self, op: UnOp) -> Option { + match op { + UnOp::Not => None, + UnOp::Length => self.length(), + UnOp::Round => self.round(), + UnOp::Abs => todo!(), + UnOp::Floor => todo!(), + UnOp::Ceil => todo!(), + UnOp::Sqrt => todo!(), + UnOp::Sin => todo!(), + UnOp::Cos => todo!(), + UnOp::Tan => todo!(), + UnOp::Asin => todo!(), + UnOp::Acos => todo!(), + UnOp::Atan => todo!(), + UnOp::Ln => todo!(), + UnOp::Log => todo!(), + UnOp::AntiLn => todo!(), + UnOp::AntiLog => todo!(), + UnOp::Minus => todo!(), + } + } + + fn length(&self) -> Option { + match self { + Self::Int(integer) => Some(integer.to_string().len().into()), + Self::Float(float) => Some(float.to_string().len().into()), + Self::String(string) => Some(string.len().into()), + } + } + + fn round(&self) -> Option { + match self { + Self::Int(_) => None, + Self::Float(float) => { + if float.is_nan() { + Some(0_i64.into()) + } else { + Some(float.round().into()) + } + } + _ => None, + } + } +} diff --git a/src/ast/var.rs b/src/ast/var.rs new file mode 100644 index 0000000..035d7e7 --- /dev/null +++ b/src/ast/var.rs @@ -0,0 +1,11 @@ +use logos::Span; +use smol_str::SmolStr; + +use super::type_::Type; + +#[derive(Debug)] +pub struct Var { + pub name: SmolStr, + pub span: Span, + pub type_: Type, +} diff --git a/src/blocks.rs b/src/blocks.rs index c8d5224..1df2b78 100644 --- a/src/blocks.rs +++ b/src/blocks.rs @@ -377,24 +377,16 @@ impl Block { ("glide_to_mouse_pointer", _) => Some(Self::GlideToMousePointer), ("point_in_direction", _) => Some(Self::PointInDirection), ("point_towards_mouse_pointer", _) => Some(Self::PointTowardsMousePointer), - ("point_towards_random_direction", _) => { - Some(Self::PointTowardsRandomDirection) - } + ("point_towards_random_direction", _) => Some(Self::PointTowardsRandomDirection), ("point_towards", _) => Some(Self::PointTowards), ("change_x", _) => Some(Self::ChangeX), ("set_x", _) => Some(Self::SetX), ("change_y", _) => Some(Self::ChangeY), ("set_y", _) => Some(Self::SetY), ("if_on_edge_bounce", _) => Some(Self::IfOnEdgeBounce), - ("set_rotation_style_left_right", _) => { - Some(Self::SetRotationStyleLeftRight) - } - ("set_rotation_style_do_not_rotate", _) => { - Some(Self::SetRotationStyleDoNotRotate) - } - ("set_rotation_style_all_around", _) => { - Some(Self::SetRotationStyleAllAround) - } + ("set_rotation_style_left_right", _) => Some(Self::SetRotationStyleLeftRight), + ("set_rotation_style_do_not_rotate", _) => Some(Self::SetRotationStyleDoNotRotate), + ("set_rotation_style_all_around", _) => Some(Self::SetRotationStyleAllAround), ("say", 2) => Some(Self::Say2), ("say", 1) => Some(Self::Say1), ("say", _) => Some(Self::Say2), @@ -856,15 +848,9 @@ impl Block { Self::ChangeY => None, Self::SetY => None, Self::IfOnEdgeBounce => None, - Self::SetRotationStyleLeftRight => { - Some("{\"STYLE\": [\"left-right\", null]}") - } - Self::SetRotationStyleDoNotRotate => { - Some("{\"STYLE\": [\"don't rotate\", null]}") - } - Self::SetRotationStyleAllAround => { - Some("{\"STYLE\": [\"all around\", null]}") - } + Self::SetRotationStyleLeftRight => Some("{\"STYLE\": [\"left-right\", null]}"), + Self::SetRotationStyleDoNotRotate => Some("{\"STYLE\": [\"don't rotate\", null]}"), + Self::SetRotationStyleAllAround => Some("{\"STYLE\": [\"all around\", null]}"), Self::Say2 => None, Self::Say1 => None, Self::Think2 => None, @@ -880,9 +866,7 @@ impl Block { Self::ChangeWhirlEffect => Some("{\"EFFECT\": [\"WHIRL\", null]}"), Self::ChangePixelateEffect => Some("{\"EFFECT\": [\"PIXELATE\", null]}"), Self::ChangeMosaicEffect => Some("{\"EFFECT\": [\"MOSAIC\", null]}"), - Self::ChangeBrightnessEffect => { - Some("{\"EFFECT\": [\"BRIGHTNESS\", null]}") - } + Self::ChangeBrightnessEffect => Some("{\"EFFECT\": [\"BRIGHTNESS\", null]}"), Self::ChangeGhostEffect => Some("{\"EFFECT\": [\"GHOST\", null]}"), Self::SetColorEffect => Some("{\"EFFECT\": [\"COLOR\", null]}"), Self::SetFisheyeEffect => Some("{\"EFFECT\": [\"FISHEYE\", null]}"), @@ -921,12 +905,8 @@ impl Block { Self::Clone0 => None, Self::Clone1 => None, Self::Ask => None, - Self::SetDragModeDraggable => { - Some("{\"DRAG_MODE\": [\"draggable\", null]}") - } - Self::SetDragModeNotDraggable => { - Some("{\"DRAG_MODE\": [\"not draggable\", null]}") - } + Self::SetDragModeDraggable => Some("{\"DRAG_MODE\": [\"draggable\", null]}"), + Self::SetDragModeNotDraggable => Some("{\"DRAG_MODE\": [\"not draggable\", null]}"), Self::ResetTimer => None, Self::EraseAll => None, Self::Stamp => None, diff --git a/src/codegen.rs b/src/codegen.rs index 49c8bff..d5ae9da 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -1,1191 +1,10 @@ -use std::{ - fs::File, - io::{self, Seek, Write}, - path::Path, -}; - -use anyhow::{bail, Result}; -use fxhash::FxHashMap; -use logos::Span; -use md5::{Digest, Md5}; -use serde_json::json; -use smol_str::SmolStr; -use zip::{ - write::{FileOptions, ZipWriter}, - CompressionMethod, -}; - -use self::{ - node::Node, - node_id::{NodeID, NodeIDFactory}, -}; -use crate::{ - ast::{ - Costume, Event, EventDetail, Expr, OnMessage, Proc, Project, Sprite, Stmt, - Stmts, - }, - blocks::{BinOp, Block, UnOp}, - config::Config, - diagnostic::{keys::is_key, Diagnostic, DiagnosticKind}, -}; - -pub mod node; -pub mod node_id; - -pub struct Sb3 -where T: Write + Seek -{ - zip: ZipWriter, - id: NodeIDFactory, - costumes: FxHashMap, - blocks_comma: bool, - inputs_comma: bool, -} - -type D<'a> = &'a mut Vec; - -#[derive(Copy, Clone)] -struct S<'a> { - stage: Option<&'a Sprite>, - sprite: &'a Sprite, - proc: Option<&'a Proc>, -} - -impl<'a> S<'a> { - fn is_arg(self, name: &str) -> bool { - self.proc.is_some_and(|it| it.used_args.contains_key(name)) - } - - fn is_local_var(self, name: &str) -> bool { - self.proc.is_some_and(|it| it.locals.contains_key(name)) - } - - fn is_var(self, name: &str) -> bool { - self.sprite.vars.contains_key(name) - || self.stage.is_some_and(|it| it.vars.contains_key(name)) - } - - fn is_list(self, name: &str) -> bool { - self.sprite.lists.contains_key(name) - || self.stage.is_some_and(|it| it.lists.contains_key(name)) - } -} - -impl Stmt { - fn is_terminator(&self) -> bool { - matches!( - self, - Stmt::Forever { .. } - | Stmt::Block { - block: Block::DeleteThisClone - | Block::StopAll - | Block::StopThisScript, - .. - } - ) - } - - fn opcode(&self, s: S) -> &'static str { - match self { - Stmt::Repeat { .. } => "control_repeat", - Stmt::Forever { .. } => "control_forever", - Stmt::Branch { else_body, .. } => { - if else_body.is_empty() { - "control_if" - } else { - "control_if_else" - } - } - Stmt::Until { .. } => "control_repeat_until", - Stmt::SetVar { .. } => "data_setvariableto", - Stmt::ChangeVar { .. } => "data_changevariableby", - Stmt::Show { name, .. } | Stmt::Hide { name, .. } => { - if s.is_var(name) || s.is_local_var(name) { - "data_showvariable" - } else { - "data_showlist" - } - } - Stmt::ListAdd { .. } => "data_addtolist", - Stmt::ListDelete { .. } => "data_deleteoflist", - Stmt::ListDeleteAll { .. } => "data_deletealloflist", - Stmt::ListInsert { .. } => "data_insertatlist", - Stmt::ListSet { .. } | Stmt::ListChange { .. } => "data_replaceitemoflist", - Stmt::Block { block, .. } => block.opcode(), - Stmt::ProcCall { .. } => "procedures_call", - } - } -} - -impl Sb3 -where T: Write + Seek -{ - pub fn new(file: T) -> Self { - Self { - zip: ZipWriter::new(file), - id: Default::default(), - costumes: Default::default(), - blocks_comma: false, - inputs_comma: false, - } - } - - pub fn package( - &mut self, - project: &Project, - config: &Config, - input: &Path, - stage_diags: D, - diags: &mut FxHashMap>, - ) -> Result<()> { - self.zip.start_file( - "project.json", - FileOptions::default() - .compression_method(CompressionMethod::Deflated) - .compression_level(Some(6)), - )?; - self.write_all(br#"{"targets":["#)?; - self.sprite(None, &project.stage, stage_diags, "Stage", config, input)?; - for (name, sprite) in project.sprites.iter() { - self.write_all(b",")?; - self.sprite( - Some(&project.stage), - sprite, - diags.get_mut(name).unwrap(), - name.as_str(), - config, - input, - )?; - } - self.write_all(br#"],"monitors":[],"extensions":[],"meta":{"semver":"3.0.0","vm":"0.2.0","agent":"goboscript"}}"#)?; - self.assets(input)?; - self.zip.finish()?; - Ok(()) - } - - fn assets(&mut self, input: &Path) -> Result<()> { - for (path, hash) in &self.costumes { - let (_, extension) = path.rsplit_once('.').unwrap(); - self.zip - .start_file(format!("{hash}.{extension}"), FileOptions::default())?; - let file = File::open(input.join(path.as_str())); - io::copy(&mut file?, &mut self.zip)?; - } - Ok(()) - } - - fn sprite( - &mut self, - stage: Option<&Sprite>, - sprite: &Sprite, - diags: D, - name: &str, - config: &Config, - input: &Path, - ) -> Result<()> { - self.id.reset(); - if name == "Stage" { - self.write_all(br#"{"isStage":true"#)?; - if !config.is_default() { - write!( - self, - r#","comments":{{"a":{{"blockId":null,"x":0,"y":0,"width":350,"height":170,"minimized":false,"text":{}}}}}"#, - json!(config.to_string()) - )?; - } - } else { - self.write_all(br#"{"isStage":false"#)?; - } - write!(self, r#","name":{},"blocks":{{"#, json!(name))?; - self.blocks_comma = false; - for proc in sprite.procs.values() { - for (name, is_used) in &proc.used_args { - let span = - proc.args.iter().find(|(arg, _)| arg == name).unwrap().1.clone(); - if !is_used { - diags.push( - DiagnosticKind::UnusedArgument(name.clone()) - .to_diagnostic(span), - ); - } - } - if !sprite.used_procs.contains(&proc.name) { - diags.push( - DiagnosticKind::UnusedProcedure(proc.name.clone()) - .to_diagnostic(proc.span.clone()), - ); - } - self.proc(S { stage, sprite, proc: Some(proc) }, diags, proc)?; - } - for event in &sprite.events { - self.event(S { stage, sprite, proc: None }, diags, event)?; - } - for on_msg in &sprite.on_messages { - self.on_message(S { stage, sprite, proc: None }, diags, on_msg.1)?; - } - - self.write_all(br#"},"costumes":["#)?; - let mut comma = false; - for costume in &sprite.costumes { - self.comma(&mut comma)?; - self.costume(diags, costume, input)?; - } - self.write_all(br#"],"variables":{"#)?; - let mut comma = false; - for proc in sprite.procs.values() { - for var in proc.locals.values() { - let resolved = json!(local_variable_resolved_name(proc, &var.name)); - self.comma(&mut comma)?; - write!(self, r#"{}:[{},{}]"#, resolved, resolved, json!(var.default))?; - } - } - for var in sprite.vars.values() { - if !var.used { - diags.push( - DiagnosticKind::UnusedVariable(var.name.clone()) - .to_diagnostic(var.span.clone()), - ); - } - self.comma(&mut comma)?; - write!( - self, - r#"{}:[{},{}]"#, - json!(*var.name), - json!(*var.name), - json!(var.default) - )?; - } - self.write_all(br#"},"lists":{"#)?; - let mut comma = false; - for list in sprite.lists.values() { - if !list.used { - diags.push( - DiagnosticKind::UnusedList(list.name.clone()) - .to_diagnostic(list.span.clone()), - ); - } - self.comma(&mut comma)?; - write!( - self, - r#"{}:[{},{}]"#, - json!(*list.name), - json!(*list.name), - json!(list.default) - )?; - } - self.write_all(br#"},"broadcasts":{"#)?; - let mut comma = false; - for broadcast_name in sprite.broadcasts.iter() { - self.comma(&mut comma)?; - write!(self, r#"{}:{}"#, json!(**broadcast_name), json!(**broadcast_name),)?; - } - // FIXME: Can you please fucking implement sounds this time? - self.write_all(br#"},"sounds":[]}"#)?; - for enum_ in sprite.enums.values() { - for (variant, span) in &enum_.variants { - if !enum_.used_variants.contains(variant) { - diags.push( - DiagnosticKind::UnusedEnumVariant { - enum_name: enum_.name.clone(), - variant_name: variant.clone(), - } - .to_diagnostic(span.clone()), - ) - } - } - } - if sprite.costumes.is_empty() { - diags.push(DiagnosticKind::NoCostumes.to_diagnostic(0..0)) - } - Ok(()) - } - - fn costume(&mut self, d: D, costume: &Costume, input: &Path) -> Result<()> { - if let Some(hash) = self.costumes.get(&costume.path) { - let (_, extension) = costume.path.rsplit_once('.').unwrap(); - write!( - self.zip, - r#"{{"name":{},"assetId":"{hash}","dataFormat":"{extension}","md5ext":"{hash}.{extension}"}}"#, - json!(*costume.name), - )?; - return Ok(()); - } - let path = input.join(costume.path.as_str()); - let mut file = match File::open(path) { - Ok(file) => file, - Err(err) => { - if matches!(err.kind(), io::ErrorKind::NotFound) { - d.push( - DiagnosticKind::FileNotFound(costume.path.clone()) - .to_diagnostic(costume.span.clone()), - ); - return Ok(()); - } - bail!(err); - } - }; - let mut hasher = Md5::new(); - io::copy(&mut file, &mut hasher)?; - let hash = format!("{:x}", hasher.finalize()); - self.costumes.insert(costume.path.clone(), hash.into()); - self.costume(d, costume, input) - } - - fn proc(&mut self, s: S, d: D, proc: &Proc) -> Result<()> { - let this_id = self.id.new_id(); - let prototype_id = self.id.new_id(); - let next_id = self.id.new_id(); - self.node( - Node::new("procedures_definition", this_id) - .some_next_id((!proc.body.is_empty()).then_some(next_id)) - .top_level(true), - )?; - self.inputs()?; - write!(self, r#""custom_block":[1,{prototype_id}]"#)?; - self.end_obj()?; - self.end_obj()?; - - let mut arg_ids = Vec::with_capacity(proc.args.len()); - for (arg, _) in &proc.args { - let arg_id = self.id.new_id(); - arg_ids.push(arg_id); - self.node( - Node::new("argument_reporter_string_number", arg_id) - .parent_id(prototype_id) - .shadow(true), - )?; - self.single_field("VALUE", arg)?; - self.end_obj()?; - } - - self.node( - Node::new("procedures_prototype", prototype_id) - .parent_id(this_id) - .shadow(true), - )?; - self.inputs()?; - let mut comma = false; - for ((arg, _), arg_id) in proc.args.iter().zip(arg_ids) { - self.comma(&mut comma)?; - write!(self, r#"{}:[2,{arg_id}]"#, json!(**arg))?; - } - self.end_obj()?; - self.mutation()?; - self.proccode(&proc.name, proc.args.len())?; - self.argument_array("argumentids", &proc.args)?; - self.argument_array("argumentnames", &proc.args)?; - self.write_all(br#","argumentdefaults":"["#)?; - let mut comma = false; - for _ in &proc.args { - self.comma(&mut comma)?; - self.write_all(br#"\"\""#)?; - } - self.write_all(br#"]""#)?; - self.warp(proc.warp)?; - self.end_obj()?; - self.end_obj()?; - self.stmts(s, d, &proc.body, next_id, Some(this_id)) - } - - fn on_message(&mut self, s: S, d: D, on_message: &OnMessage) -> Result<()> { - let this_id = self.id.new_id(); - let next_id = self.id.new_id(); - self.node( - Node::new("event_whenbroadcastreceived", this_id) - .some_next_id((!on_message.body.is_empty()).then_some(next_id)) - .top_level(true), - )?; - - write!( - self, - r#","fields":{{"BROADCAST_OPTION":[{},{}]}}}}"#, - json!(*on_message.message), - json!(*on_message.message) - )?; - self.stmts(s, d, &on_message.body, next_id, Some(this_id)) - } - - fn event(&mut self, s: S, d: D, event: &Event) -> Result<()> { - let this_id = self.id.new_id(); - let next_id = self.id.new_id(); - self.node( - Node::new(event.opcode(), this_id) - .some_next_id((!event.body.is_empty()).then_some(next_id)) - .top_level(true), - )?; - match &event.kind { - EventDetail::OnKey { key, span } => { - if !is_key(key) { - d.push( - DiagnosticKind::UnrecognizedKey(key.clone()) - .to_diagnostic(span.clone()), - ); - } - write!( - self, - r#","fields":{{"KEY_OPTION":[{},null]}}}}"#, - json!(**key) - )?; - } - EventDetail::OnBackdrop { backdrop, span: _ } => { - write!( - self, - r#","fields":{{"BACKDROP_OPTION":[{},null]}}}}"#, - json!(**backdrop) - )?; - } - EventDetail::OnLoudnessGt { value } | EventDetail::OnTimerGt { value } => { - let value_id = self.id.new_id(); - self.inputs()?; - self.input(s, d, "VALUE", &value.borrow(), value_id)?; - self.end_obj()?; - self.write_all( - if matches!(event.kind, EventDetail::OnLoudnessGt { .. }) { - br#","fields":{"WHENGREATERTHANMENU":["LOUDNESS",null]}}"# - } else { - br#","fields":{"WHENGREATERTHANMENU":["TIMER",null]}}"# - }, - )?; - self.expr(s, d, &value.borrow(), value_id, this_id)?; - } - _ => { - self.end_obj()?; - } - } - self.stmts(s, d, &event.body, next_id, Some(this_id)) - } - - fn stmts( - &mut self, - s: S, - d: D, - stmts: &Stmts, - mut this_id: NodeID, - mut parent_id: Option, - ) -> Result<()> { - for (i, stmt) in stmts.iter().enumerate() { - let is_last = i == stmts.len() - 1; - if is_last || stmt.is_terminator() { - self.stmt(s, d, stmt, this_id, None, parent_id)?; - if !is_last { - d.push( - DiagnosticKind::FollowedByUnreachableCode - .to_diagnostic(stmt.span().clone()), - ) - } - break; - } - let next_id = self.id.new_id(); - self.stmt(s, d, stmt, this_id, Some(next_id), parent_id)?; - parent_id = Some(this_id); - this_id = next_id; - } - Ok(()) - } - - fn stmt( - &mut self, - s: S, - d: D, - stmt: &Stmt, - this_id: NodeID, - next_id: Option, - parent_id: Option, - ) -> Result<()> { - self.node( - Node::new(stmt.opcode(s), this_id) - .some_next_id(next_id) - .some_parent_id(parent_id), - )?; - self.inputs()?; - match stmt { - Stmt::Forever { body, .. } => { - let body_id = self.id.new_id(); - self.substack("SUBSTACK", (!body.is_empty()).then_some(body_id))?; - self.end_obj()?; - self.end_obj()?; - self.stmts(s, d, body, body_id, Some(this_id))?; - } - Stmt::Branch { cond, if_body, else_body } => { - let cond_id = self.id.new_id(); - let if_body_id = self.id.new_id(); - let else_body_id = self.id.new_id(); - self.input(s, d, "CONDITION", &cond.borrow(), cond_id)?; - self.substack("SUBSTACK", (!if_body.is_empty()).then_some(if_body_id))?; - self.substack( - "SUBSTACK2", - (!else_body.is_empty()).then_some(else_body_id), - )?; - self.end_obj()?; - self.end_obj()?; - self.expr(s, d, &cond.borrow(), cond_id, this_id)?; - self.stmts(s, d, if_body, if_body_id, Some(this_id))?; - self.stmts(s, d, else_body, else_body_id, Some(this_id))?; - } - Stmt::Repeat { times: input, body } | Stmt::Until { cond: input, body } => { - let input_id = self.id.new_id(); - let body_id = self.id.new_id(); - self.input( - s, - d, - if matches!(stmt, Stmt::Until { .. }) { - "CONDITION" - } else { - "TIMES" - }, - &input.borrow(), - input_id, - )?; - self.substack("SUBSTACK", (!body.is_empty()).then_some(body_id))?; - self.end_obj()?; - self.end_obj()?; - self.expr(s, d, &input.borrow(), input_id, this_id)?; - self.stmts(s, d, body, body_id, Some(this_id))?; - } - | Stmt::SetVar { name, span, value, .. } - | Stmt::ChangeVar { name, span, value } => { - let value_id = self.id.new_id(); - self.input(s, d, "VALUE", &value.borrow(), value_id)?; - self.end_obj()?; - self.resolve_variable(s, d, name, span)?; - self.end_obj()?; - self.expr(s, d, &value.borrow(), value_id, this_id)?; - } - Stmt::Show { name, span } | Stmt::Hide { name, span } => { - self.end_obj()?; - self.resolve_variable_or_list(s, d, name, span)?; - self.end_obj()?; - } - | Stmt::ListAdd { name, span, value: input } - | Stmt::ListDelete { name, span, index: input } => { - let input_id = self.id.new_id(); - self.list(s, d, name, span); - self.input( - s, - d, - if matches!(stmt, Stmt::ListAdd { .. }) { "ITEM" } else { "INDEX" }, - &input.borrow(), - input_id, - )?; - self.end_obj()?; - self.single_field_id("LIST", name)?; - self.end_obj()?; - self.expr(s, d, &input.borrow(), input_id, this_id)?; - } - Stmt::ListDeleteAll { name, span } => { - self.list(s, d, name, span); - self.end_obj()?; - self.single_field_id("LIST", name)?; - self.end_obj()?; - } - | Stmt::ListInsert { name, span, index, value } - | Stmt::ListSet { name, span, index, value } => { - let index_id = self.id.new_id(); - let value_id = self.id.new_id(); - self.list(s, d, name, span); - self.input(s, d, "INDEX", &index.borrow(), index_id)?; - self.input(s, d, "ITEM", &value.borrow(), value_id)?; - self.end_obj()?; - self.single_field_id("LIST", name)?; - self.end_obj()?; - self.expr(s, d, &index.borrow(), index_id, this_id)?; - self.expr(s, d, &value.borrow(), value_id, this_id)?; - } - Stmt::ListChange { index, name, op, span, value } => { - let index_id = self.id.new_id(); - let value_id = self.id.new_id(); - let nameexpr = Expr::Name { name: name.clone(), span: span.clone() }; - let expr = match op { - BinOp::FloorDiv => UnOp::Floor - .to_expr( - BinOp::Div.to_expr(nameexpr.into(), index.clone()).into(), - ) - .into(), - _ => Expr::BinOp { - op: *op, - lhs: Expr::BinOp { - op: BinOp::Of, - lhs: nameexpr.into(), - rhs: index.clone(), - } - .into(), - rhs: value.clone(), - }, - }; - self.list(s, d, name, span); - self.input(s, d, "INDEX", &index.borrow(), index_id)?; - self.input(s, d, "ITEM", &expr, value_id)?; - self.end_obj()?; - self.single_field_id("LIST", name)?; - self.end_obj()?; - self.expr(s, d, &index.borrow(), index_id, this_id)?; - self.expr(s, d, &expr, value_id, this_id)?; - } - Stmt::Block { block, span, args } => { - if args.len() != block.args().len() { - d.push( - DiagnosticKind::BlockArgsCountMismatch { - block: *block, - given: args.len(), - } - .to_diagnostic(span.clone()), - ); - } - let arg_ids: Vec<_> = (&mut self.id).take(args.len()).collect(); - let menu_id = block.menu().map(|_| self.id.new_id()); - let mut menu_value = None; - let mut menu_is_default = menu_id.is_some(); - for ((&name, arg), arg_id) in - block.args().iter().zip(args).zip(&arg_ids) - { - if block.menu().is_some_and(|it| it.input == name) { - if let Some(arg) = arg.borrow().try_to_string() { - menu_value = Some(arg); - continue; - } else { - menu_is_default = false; - self.input_with_shadow( - s, - d, - name, - &arg.borrow(), - *arg_id, - menu_id.unwrap(), - )?; - } - } else { - self.input(s, d, name, &arg.borrow(), *arg_id)?; - } - } - if menu_is_default { - if self.inputs_comma { - self.write_all(b",")?; - } - self.inputs_comma = true; - write!( - self, - r#""{}":[1,{}]"#, - block.menu().unwrap().input, - menu_id.unwrap() - )?; - } - self.end_obj()?; - if let Some(fields) = block.fields() { - write!(self, r#","fields":{fields}"#)?; - } - self.end_obj()?; - for (arg, arg_id) in args.iter().zip(arg_ids) { - self.expr(s, d, &arg.borrow(), arg_id, this_id)?; - } - if let Some(menu) = block.menu() { - self.node( - Node::new(menu.opcode, menu_id.unwrap()) - .parent_id(this_id) - .shadow(true), - )?; - self.single_field( - menu.input, - menu_value.as_deref().unwrap_or(menu.default), - )?; - self.end_obj()?; - } - } - Stmt::ProcCall { name, span, args } => { - let Some(proc) = s.sprite.procs.get(name) else { - d.push( - DiagnosticKind::UnrecognizedProcedure(name.clone()) - .to_diagnostic(span.clone()), - ); - return Ok(()); - }; - if args.len() != proc.args.len() { - d.push( - DiagnosticKind::ProcArgsCountMismatch { - proc: name.clone(), - given: args.len(), - } - .to_diagnostic(span.clone()), - ); - } - let arg_ids: Vec<_> = (&mut self.id).take(args.len()).collect(); - for (((name, _), arg), arg_id) in - proc.args.iter().zip(args).zip(&arg_ids) - { - self.input(s, d, name, &arg.borrow(), *arg_id)?; - } - self.end_obj()?; - self.mutation()?; - self.proccode(name, args.len())?; - self.argument_array("argumentids", &proc.args)?; - self.warp(proc.warp)?; - self.end_obj()?; - self.end_obj()?; - for (arg, arg_id) in args.iter().zip(arg_ids) { - self.expr(s, d, &arg.borrow(), arg_id, this_id)?; - } - } - } - Ok(()) - } - - fn expr( - &mut self, - s: S, - d: D, - expr: &Expr, - this_id: NodeID, - parent_id: NodeID, - ) -> Result<()> { - match expr { - | Expr::Int(_) - | Expr::Float(_) - | Expr::Str(_) - | Expr::Name { .. } - | Expr::EnumVariant { .. } => {} - Expr::Arg { name, span } => { - if !s.is_arg(name) { - d.push( - DiagnosticKind::UnrecognizedArgument { - name: name.clone(), - proc: s.proc.map(|proc| proc.name.clone()), - } - .to_diagnostic(span.clone()), - ); - } - self.node( - Node::new("argument_reporter_string_number", this_id) - .parent_id(parent_id), - )?; - write!(self, r#","fields":{{"VALUE":[{},null]}}}}"#, json!(**name))?; - } - Expr::Repr { repr, span, args } => { - if args.len() != repr.args().len() { - d.push( - DiagnosticKind::ReprArgsCountMismatch { - repr: *repr, - given: args.len(), - } - .to_diagnostic(span.clone()), - ); - } - let arg_ids: Vec<_> = (&mut self.id).take(args.len()).collect(); - self.node(Node::new(repr.opcode(), this_id).parent_id(parent_id))?; - self.inputs()?; - let menu_id = repr.menu().map(|_| self.id.new_id()); - let mut menu_value = None; - let mut menu_is_default = menu_id.is_some(); - for ((&name, arg), arg_id) in repr.args().iter().zip(args).zip(&arg_ids) - { - if repr.menu().is_some_and(|it| it.input == name) { - if let Some(arg) = arg.borrow().try_to_string() { - menu_value = Some(arg); - continue; - } else { - menu_is_default = false; - self.input_with_shadow( - s, - d, - name, - &arg.borrow(), - *arg_id, - menu_id.unwrap(), - )?; - } - } else { - self.input(s, d, name, &arg.borrow(), *arg_id)?; - } - } - if menu_is_default { - if self.inputs_comma { - self.write_all(b",")?; - } - self.inputs_comma = true; - write!( - self, - r#""{}":[1,{}]"#, - repr.menu().unwrap().input, - menu_id.unwrap() - )?; - } - self.end_obj()?; - if let Some(fields) = repr.fields() { - write!(self, r#","fields":{fields}"#)?; - } - self.end_obj()?; - for (arg, arg_id) in args.iter().zip(arg_ids) { - self.expr(s, d, &arg.borrow(), arg_id, this_id)?; - } - if let Some(menu) = repr.menu() { - self.node( - Node::new(menu.opcode, menu_id.unwrap()) - .parent_id(this_id) - .shadow(true), - )?; - self.single_field( - menu.input, - menu_value.as_deref().unwrap_or(menu.default), - )?; - self.end_obj()?; - } - } - Expr::UnOp { op, val } => { - if matches!(op, UnOp::Length) { - if let Expr::Name { name, .. } = &*val.borrow() { - if s.sprite.lists.contains_key(name) - || s.stage.is_some_and(|it| it.lists.contains_key(name)) - { - self.node( - Node::new("data_lengthoflist", this_id) - .parent_id(parent_id), - )?; - self.single_field_id("LIST", name)?; - self.end_obj()?; - self.expr(s, d, &val.borrow(), this_id, this_id)?; - return Ok(()); - } - } - } - let val_id = self.id.new_id(); - self.node(Node::new(op.opcode(), this_id).parent_id(parent_id))?; - self.inputs()?; - self.input(s, d, op.input(), &val.borrow(), val_id)?; - self.end_obj()?; - if let Some(fields) = op.fields() { - write!(self, r#","fields":{fields}"#)?; - } - self.end_obj()?; - self.expr(s, d, &val.borrow(), val_id, this_id)?; - } - Expr::BinOp { op, lhs, rhs } => { - let right_id = self.id.new_id(); - if matches!(op, BinOp::Of) { - if let Expr::Name { name, .. } = &*lhs.borrow() { - if s.sprite.lists.contains_key(name) - || s.stage.is_some_and(|it| it.lists.contains_key(name)) - { - self.node( - Node::new("data_itemoflist", this_id) - .parent_id(parent_id), - )?; - self.inputs()?; - self.input(s, d, "INDEX", &rhs.borrow(), right_id)?; - self.end_obj()?; - self.single_field_id("LIST", name)?; - self.end_obj()?; - self.expr(s, d, &rhs.borrow(), right_id, this_id)?; - return Ok(()); - } - } - } - let left_id = self.id.new_id(); - self.node(Node::new(op.opcode(), this_id).parent_id(parent_id))?; - self.inputs()?; - self.input(s, d, op.lhs(), &lhs.borrow(), left_id)?; - self.input(s, d, op.rhs(), &rhs.borrow(), right_id)?; - self.end_obj()?; - self.end_obj()?; - self.expr(s, d, &lhs.borrow(), left_id, this_id)?; - self.expr(s, d, &rhs.borrow(), right_id, this_id)?; - } - } - Ok(()) - } - - fn list(&mut self, s: S, d: D, name: &SmolStr, span: &Span) { - if s.sprite.lists.contains_key(name) - || s.stage.is_some_and(|it| it.lists.contains_key(name)) - { - return; - } - d.push( - DiagnosticKind::UnrecognizedList(name.clone()).to_diagnostic(span.clone()), - ); - } - - fn inputs(&mut self) -> io::Result<()> { - self.inputs_comma = false; - self.write_all(br#","inputs":{"#) - } - - fn input( - &mut self, - s: S, - d: D, - name: &str, - expr: &Expr, - this_id: NodeID, - ) -> io::Result<()> { - self._input(s, d, name, expr, this_id, None) - } - - fn input_with_shadow( - &mut self, - s: S, - d: D, - name: &'static str, - expr: &Expr, - this_id: NodeID, - shadow_id: NodeID, - ) -> io::Result<()> { - self._input(s, d, name, expr, this_id, Some(shadow_id)) - } - - fn substack( - &mut self, - name: &'static str, - this_id: Option, - ) -> io::Result<()> { - if let Some(this_id) = this_id { - if self.inputs_comma { - self.write_all(b",")?; - } - self.inputs_comma = true; - write!(self, r#""{name}":[2,{this_id}]"#)?; - } - Ok(()) - } - - fn _input( - &mut self, - s: S, - d: D, - name: &str, - expr: &Expr, - this_id: NodeID, - shadow_id: Option, - ) -> io::Result<()> { - if self.inputs_comma { - self.write_all(b",")?; - } - self.inputs_comma = true; - write!(self, r#""{name}":"#)?; - match expr { - Expr::Int(value) => { - write!(self, r#"[1,[4,{}]]"#, json!(value)) - } - Expr::Float(value) => { - write!(self, r#"[1,[4,{}]]"#, json!(value)) - } - Expr::Str(value) => { - let color = if name == "COLOR" || name == "COLOR2" { - csscolorparser::parse(value).ok().filter(|color| color.a == 1.0) - } else { - None - }; - if name == "BROADCAST_INPUT" { - write!(self, r#"[1,[11,{},{}]]"#, json!(**value), json!(**value)) - } else if let Some(color) = color { - write!(self, r#"[1,[9,{}]]"#, json!(color.to_hex_string())) - } else { - write!(self, r#"[1,[10,{}]]"#, json!(**value)) - } - } - Expr::EnumVariant { enum_name, enum_span, variant_name, variant_span } => { - if let Some(enum_) = s.sprite.enums.get(enum_name) { - let index = enum_ - .variants - .iter() - .position(|(variant, _)| variant == variant_name); - if let Some(index) = index { - write!(self, r#"[1,[10,{index}]]"#) - } else { - d.push( - DiagnosticKind::UnrecognizedEnumVariant { - enum_name: enum_name.clone(), - variant_name: variant_name.clone(), - } - .to_diagnostic(variant_span.clone()), - ); - Ok(()) - } - } else { - d.push( - DiagnosticKind::UnrecognizedEnum { - enum_name: enum_name.clone(), - variant_name: variant_name.clone(), - } - .to_diagnostic(enum_span.clone()), - ); - Ok(()) - } - } - Expr::Name { name: var, span } => { - if let Some(resolved) = - self.resolve_local_variable(s, var).map(|it| json!(it)) - { - write!(self, "[3,[12,{},{}],", resolved, resolved)?; - } else if s.is_var(var) { - write!(self, "[3,[12,{},{}],", json!(**var), json!(**var))?; - } else if s.is_list(var) { - write!(self, "[3,[13,{},{}],", json!(**var), json!(**var))?; - } else { - d.push( - DiagnosticKind::UnrecognizedVariable(var.clone()) - .to_diagnostic(span.clone()), - ); - } - self.input_shadow(s, shadow_id, name) - } - _ => { - if name == "CONDITION" || name == "CONDITION2" { - return write!(self, r#"[2,{this_id}]"#); - } - write!(self, r#"[3,{this_id},"#)?; - self.input_shadow(s, shadow_id, name) - } - } - } - - fn input_shadow( - &mut self, - s: S, - shadow_id: Option, - name: &str, - ) -> io::Result<()> { - if let Some(shadow_id) = shadow_id { - write!(self, "{shadow_id}]") - } else if name == "BROADCAST_INPUT" { - let broadcast_name = match s.stage { - Some(stage) => { - stage.broadcasts.iter().min().expect("no broadcasts?").clone() - } - None => "message1".into(), - }; - write!( - self, - r#"[11,{},{}]]"#, - json!(*broadcast_name), - json!(*broadcast_name) - ) - } else { - self.write_all(br#"[10,""]]"#) - } - } - - fn single_field(&mut self, name: &'static str, value: &str) -> io::Result<()> { - write!(self, r#","fields":{{"{name}":[{},null]}}"#, json!(value)) - } - - fn resolve_local_variable(&mut self, s: S, name: &SmolStr) -> Option { - s.proc.and_then(|proc| { - proc.locals - .contains_key(name) - .then(|| local_variable_resolved_name(proc, name)) - }) - } - - fn resolve_variable( - &mut self, - s: S, - d: D, - name: &SmolStr, - span: &Span, - ) -> io::Result<()> { - if s.is_local_var(name) { - return self.single_field( - "VARIABLE", - &local_variable_resolved_name(s.proc.unwrap(), name), - ); - } - if s.is_var(name) { - return self.single_field_id("VARIABLE", name); - } - d.push( - DiagnosticKind::UnrecognizedVariable(name.clone()) - .to_diagnostic(span.clone()), - ); - Ok(()) - } - - fn resolve_variable_or_list( - &mut self, - s: S, - d: D, - name: &SmolStr, - span: &Span, - ) -> io::Result<()> { - if s.is_local_var(name) { - return self.single_field( - "VARIABLE", - &local_variable_resolved_name(s.proc.unwrap(), name), - ); - } - if s.is_var(name) { - return self.single_field_id("VARIABLE", name); - } - if s.is_list(name) { - return self.single_field_id("LIST", name); - } - d.push( - DiagnosticKind::UnrecognizedVariable(name.clone()) - .to_diagnostic(span.clone()), - ); - Ok(()) - } - - fn single_field_id(&mut self, name: &'static str, value: &str) -> io::Result<()> { - write!(self, r#","fields":{{"{name}":[{},{}]}}"#, json!(value), json!(value)) - } - - fn mutation(&mut self) -> io::Result<()> { - self.write_all(br#","mutation":{"tagName":"mutation","children":[]"#) - } - - fn warp(&mut self, warp: bool) -> io::Result<()> { - if warp { - self.write_all(br#","warp":"true""#) - } else { - self.write_all(br#","warp":"false""#) - } - } - - fn argument_array( - &mut self, - key: &'static str, - args: &Vec<(SmolStr, Span)>, - ) -> io::Result<()> { - write!(self, r#","{key}":"["#)?; - let mut comma = false; - for (arg, _) in args { - self.comma(&mut comma)?; - write!(self, r#"\"{arg}\""#)?; - } - self.write_all(br#"]""#) - } - - fn proccode(&mut self, name: &str, args: usize) -> io::Result<()> { - write!(self, r#","proccode":"{name}"#)?; - for _ in 0..args { - self.write_all(b" %s")?; - } - self.write_all(br#"""#) - } - - fn comma(&mut self, comma: &mut bool) -> io::Result<()> { - if *comma { - self.write_all(b",")?; - } - *comma = true; - Ok(()) - } - - fn end_obj(&mut self) -> io::Result<()> { - self.write_all(b"}") - } -} - -impl Write for Sb3 -where T: Write + Seek -{ - fn write(&mut self, buf: &[u8]) -> io::Result { - self.zip.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.zip.flush() - } -} - -fn local_variable_resolved_name(proc: &Proc, name: &SmolStr) -> String { - format!("{}.{}", proc.name, name) -} +mod event; +mod expr; +mod input; +mod mutation; +mod node; +mod node_id; +mod node_id_factory; +pub mod sb3; +mod stmt; +pub mod turbowarp_config; diff --git a/src/codegen/event.rs b/src/codegen/event.rs new file mode 100644 index 0000000..fc87db1 --- /dev/null +++ b/src/codegen/event.rs @@ -0,0 +1,80 @@ +use std::io::{self, Seek, Write}; + +use logos::Span; +use smol_str::SmolStr; + +use super::{ + node_id::NodeID, + sb3::{Sb3, D, S}, +}; +use crate::{ast::Expr, misc::Rrc}; + +impl Sb3 +where T: Write + Seek +{ + pub fn on_flag(&mut self, _s: S, _d: D, _this_id: NodeID) -> io::Result<()> { + self.end_obj() // node + } + + pub fn on_key( + &mut self, + _s: S, + _d: D, + _this_id: NodeID, + key: &SmolStr, + _span: &Span, + ) -> io::Result<()> { + self.single_field("KEY_OPTION", key)?; + self.end_obj() // node + } + + pub fn on_click(&mut self, _s: S, _d: D, _this_id: NodeID) -> io::Result<()> { + self.end_obj() // node + } + + pub fn on_backdrop( + &mut self, + _s: S, + _d: D, + _this_id: NodeID, + backdrop: &SmolStr, + _span: &Span, + ) -> io::Result<()> { + self.single_field("BACKDROP_OPTION", backdrop)?; + self.end_obj() // node + } + + pub fn on_loudness_gt( + &mut self, + s: S, + d: D, + this_id: NodeID, + value: &Rrc, + ) -> io::Result<()> { + self.begin_inputs()?; + self.input(s, d, "VALUE", &value.borrow(), this_id)?; + self.end_obj()?; // inputs + self.single_field("WHENGREATERTHANMENU", "LOUDNESS")?; + self.end_obj()?; // node + self.expr(s, d, &value.borrow(), this_id, this_id) + } + + pub fn on_timer_gt( + &mut self, + s: S, + d: D, + this_id: NodeID, + value: &Rrc, + ) -> io::Result<()> { + self.begin_inputs()?; + self.input(s, d, "VALUE", &value.borrow(), this_id)?; + self.end_obj()?; // inputs + self.single_field("WHENGREATERTHANMENU", "TIMER")?; + self.end_obj()?; // node + self.expr(s, d, &value.borrow(), this_id, this_id) + } + + pub fn on_clone(&mut self, _s: S, _d: D, _this_id: NodeID) -> io::Result<()> { + self.end_obj() // node + } +} diff --git a/src/codegen/expr.rs b/src/codegen/expr.rs new file mode 100644 index 0000000..f563280 --- /dev/null +++ b/src/codegen/expr.rs @@ -0,0 +1,196 @@ +use std::io::{self, Seek, Write}; + +use logos::Span; + +use super::{ + node::Node, + node_id::NodeID, + sb3::{qualify_struct_var_name, QualifiedName, Sb3, D, S}, +}; +use crate::{ + ast::*, + blocks::{BinOp, Repr, UnOp}, + diagnostic::DiagnosticKind, + misc::{write_comma_io, Rrc}, +}; + +impl Sb3 +where T: Write + Seek +{ + pub fn arg( + &mut self, + s: S, + d: D, + this_id: NodeID, + parent_id: NodeID, + name: &Name, + ) -> io::Result<()> { + let basename = name.basename(); + let Some(proc) = s.proc else { + d.report( + DiagnosticKind::UnrecognizedArgument(basename.clone()), + &name.span(), + ); + return Ok(()); + }; + if !proc.args.iter().any(|arg| &arg.name == basename) { + d.report( + DiagnosticKind::UnrecognizedArgument(basename.clone()), + &name.span(), + ); + return Ok(()); + } + let qualified_name = match name.fieldname() { + Some(fieldname) => qualify_struct_var_name(fieldname, basename), + None => basename.clone(), + }; + self.begin_node( + Node::new("argument_reporter_string_number", this_id).parent_id(parent_id), + )?; + self.single_field("VALUE", &qualified_name)?; + self.end_obj() // node + } + + pub fn repr( + &mut self, + s: S, + d: D, + this_id: NodeID, + parent_id: NodeID, + repr: &Repr, + span: &Span, + args: &Vec>, + ) -> io::Result<()> { + if args.len() != repr.args().len() { + todo!() + } + self.begin_node(Node::new(repr.opcode(), this_id).parent_id(parent_id))?; + let arg_ids: Vec = (&mut self.id).take(args.len()).collect(); + let menu_id = repr.menu().map(|_| self.id.new_id()); + let mut menu_value = None; + let mut menu_is_default = menu_id.is_some(); + self.begin_inputs()?; + for ((&arg_name, arg_value), &arg_id) in repr.args().iter().zip(args).zip(&arg_ids) { + if repr.menu().is_some_and(|menu| menu.input == arg_name) { + if let Expr::Value { value, span: _ } = &*arg_value.borrow() { + menu_value = Some(value.clone()); + continue; + } else { + menu_is_default = false; + self.input_with_shadow( + s, + d, + arg_name, + &arg_value.borrow(), + arg_id, + menu_id.unwrap(), + )?; + } + } else { + self.input(s, d, arg_name, &arg_value.borrow(), arg_id)?; + } + } + if menu_is_default { + write_comma_io(&mut self.zip, &mut self.inputs_comma)?; + write!( + self, + r#""{}":[1,{}]"#, + repr.menu().unwrap().input, + menu_id.unwrap() + )?; + } + self.end_obj()?; // inputs + if let Some(fields) = repr.fields() { + write!(self, r#","fields":{fields}"#)?; + } + self.end_obj()?; // node + for (arg, arg_id) in args.iter().zip(arg_ids) { + self.expr(s, d, &arg.borrow(), arg_id, this_id)?; + } + if let Some(menu) = repr.menu() { + self.begin_node( + Node::new(menu.opcode, menu_id.unwrap()) + .parent_id(this_id) + .shadow(true), + )?; + if let Some(menu_value) = menu_value { + self.single_field(menu.input, &menu_value.to_string())?; + } else { + self.single_field(menu.input, menu.default)?; + } + self.end_obj()?; // node + } + Ok(()) + } + + pub fn un_op( + &mut self, + s: S, + d: D, + this_id: NodeID, + parent_id: NodeID, + op: &UnOp, + _span: &Span, + opr: &Rrc, + ) -> io::Result<()> { + let opr_id = self.id.new_id(); + self.begin_node(Node::new(op.opcode(), this_id).parent_id(parent_id))?; + self.begin_inputs()?; + self.input(s, d, op.input(), &opr.borrow(), opr_id)?; + self.end_obj()?; // inputs + if let Some(fields) = op.fields() { + write!(self, r#","fields":{fields}"#)?; + } + self.end_obj()?; // node + self.expr(s, d, &opr.borrow(), opr_id, this_id) + } + + pub fn bin_op( + &mut self, + s: S, + d: D, + this_id: NodeID, + parent_id: NodeID, + op: &BinOp, + _span: &Span, + lhs: &Rrc, + rhs: &Rrc, + ) -> io::Result<()> { + if let BinOp::Of = op { + if let Expr::Name(name) = &*lhs.borrow() { + if let Some(QualifiedName::List(qualified_name, _)) = s.qualify_name(d, name) { + return self.list_index(s, d, this_id, parent_id, &qualified_name, rhs); + } + } + } + let lhs_id = self.id.new_id(); + let rhs_id = self.id.new_id(); + self.begin_node(Node::new(op.opcode(), this_id).parent_id(parent_id))?; + self.begin_inputs()?; + self.input(s, d, op.lhs(), &lhs.borrow(), lhs_id)?; + self.input(s, d, op.rhs(), &rhs.borrow(), rhs_id)?; + self.end_obj()?; // inputs + self.end_obj()?; // node + self.expr(s, d, &lhs.borrow(), lhs_id, this_id)?; + self.expr(s, d, &rhs.borrow(), rhs_id, this_id) + } + + pub fn list_index( + &mut self, + s: S, + d: D, + this_id: NodeID, + parent_id: NodeID, + name: &str, + index: &Rrc, + ) -> io::Result<()> { + let index_id = self.id.new_id(); + self.begin_node(Node::new("data_itemoflist", this_id).parent_id(parent_id))?; + self.begin_inputs()?; + self.input(s, d, "INDEX", &index.borrow(), index_id)?; + self.end_obj()?; // inputs + self.single_field_id("LIST", name)?; + self.end_obj()?; // node + self.expr(s, d, &index.borrow(), index_id, this_id) + } +} diff --git a/src/codegen/input.rs b/src/codegen/input.rs new file mode 100644 index 0000000..59f925c --- /dev/null +++ b/src/codegen/input.rs @@ -0,0 +1,145 @@ +use std::io::{self, Seek, Write}; + +use logos::Span; +use serde_json::json; +use smol_str::SmolStr; + +use super::{ + node_id::NodeID, + sb3::{QualifiedName, Sb3, D, S}, +}; +use crate::{ + ast::{Expr, Name, Type, Value}, + codegen::sb3::qualify_struct_var_name, + diagnostic::DiagnosticKind, + misc::write_comma_io, +}; + +impl Sb3 +where T: Write + Seek +{ + pub fn input( + &mut self, + s: S, + d: D, + name: &str, + expr: &Expr, + this_id: NodeID, + ) -> io::Result<()> { + self._input(s, d, name, expr, this_id, None) + } + + pub fn input_with_shadow( + &mut self, + s: S, + d: D, + name: &str, + expr: &Expr, + this_id: NodeID, + shadow_id: NodeID, + ) -> io::Result<()> { + self._input(s, d, name, expr, this_id, Some(shadow_id)) + } + + fn _input( + &mut self, + s: S, + d: D, + input_name: &str, + expr: &Expr, + this_id: NodeID, + shadow_id: Option, + ) -> io::Result<()> { + write_comma_io(&mut self.zip, &mut self.inputs_comma)?; + write!(self, r#""{input_name}":"#)?; + match expr { + Expr::Value { value, span: _ } => self.value_input(s, d, input_name, value), + Expr::Name(name) => self.name_input(s, d, input_name, name, shadow_id), + _ => self.node_input(s, d, input_name, this_id, shadow_id), + } + } + + fn value_input(&mut self, _s: S, _d: D, name: &str, value: &Value) -> io::Result<()> { + match value { + Value::Int(int_value) => { + write!(self, "[1,[4,{}]]", json!(int_value)) + } + Value::Float(float_value) => { + write!(self, "[1,[4,{}]]", json!(float_value)) + } + Value::String(string_value) => { + let color = ["COLOR", "COLOR2"] + .contains(&name) + .then(|| { + csscolorparser::parse(string_value) + .ok() + .filter(|color| color.a == 1.0) + }) + .flatten(); + if name == "BROADCAST_INPUT" { + write!( + self, + "[1,[11,{},{}]]", + json!(**string_value), + json!(**string_value) + ) + } else if let Some(color) = color { + write!(self, "[1,[9,{}]]", json!(color.to_hex_string())) + } else { + write!(self, "[1,[10,{}]]", json!(**string_value)) + } + } + } + } + + fn name_input( + &mut self, + s: S, + d: D, + input_name: &str, + name: &Name, + shadow_id: Option, + ) -> io::Result<()> { + match s.qualify_name(d, name) { + Some(QualifiedName::Var(name, _)) => { + write!(self, "[3,[12,{},{}],", json!(*name), json!(*name))?; + } + Some(QualifiedName::List(name, _)) => { + write!(self, "[3,[13,{},{}],", json!(*name), json!(*name))?; + } + None => {} + } + self.shadow_input(s, input_name, shadow_id) + } + + fn node_input( + &mut self, + s: S, + _d: D, + input_name: &str, + node_id: NodeID, + shadow_id: Option, + ) -> io::Result<()> { + if ["CONDITION", "CONDITION2"].contains(&input_name) { + return write!(self, "[2,{node_id}]"); + } + write!(self, "[3,{node_id},")?; + self.shadow_input(s, input_name, shadow_id) + } + + fn shadow_input( + &mut self, + _s: S, + input_name: &str, + shadow_id: Option, + ) -> io::Result<()> { + if let Some(shadow_id) = shadow_id { + write!(self, "{shadow_id}]") + } else if input_name == "BROADCAST_INPUT" { + let broadcast_name = json!("message1"); + write!(self, "[11,{},{}]]", broadcast_name, broadcast_name) + } else { + write!(self, r#"[10, ""]]"#) + } + } +} diff --git a/src/codegen/mutation.rs b/src/codegen/mutation.rs new file mode 100644 index 0000000..eb540cf --- /dev/null +++ b/src/codegen/mutation.rs @@ -0,0 +1,69 @@ +use std::fmt::{self, Display}; + +use smol_str::SmolStr; + +use super::node_id::NodeID; +use crate::misc::write_comma_fmt; + +pub struct Mutation<'a> { + name: SmolStr, + args: &'a Vec<(SmolStr, NodeID)>, + warp: bool, + is_call: bool, +} + +impl<'a> Mutation<'a> { + pub fn prototype(name: SmolStr, args: &'a Vec<(SmolStr, NodeID)>, warp: bool) -> Self { + Self { + name, + args, + warp, + is_call: false, + } + } + + pub fn call(name: SmolStr, args: &'a Vec<(SmolStr, NodeID)>, warp: bool) -> Self { + Self { + name, + args, + warp, + is_call: true, + } + } +} + +impl<'a> Display for Mutation<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, r#","mutation":{{"tagName":"mutation","children":[]"#)?; + write!(f, r#","warp":"{}""#, self.warp)?; + write!(f, r#","proccode":"{}"#, self.name)?; + for _ in 0..self.args.len() { + write!(f, " %s")?; + } + write!(f, "\"")?; + write!(f, r#","argumentids":"["#)?; + let mut comma = false; + for (arg_name, _) in self.args { + write_comma_fmt(&mut *f, &mut comma)?; + write!(f, r#"\"{}\""#, arg_name)?; + } + write!(f, "]\"")?; + if !self.is_call { + write!(f, r#","argumentnames":"["#)?; + let mut comma = false; + for (arg_name, _) in self.args { + write_comma_fmt(&mut *f, &mut comma)?; + write!(f, r#"\"{}\""#, arg_name)?; + } + write!(f, "]\"")?; + write!(f, r#","argumentdefaults":"["#)?; + let mut comma = false; + for _ in self.args { + write_comma_fmt(&mut *f, &mut comma)?; + write!(f, r#"\"\""#,)?; + } + write!(f, "]\"")?; + } + write!(f, "}}") // mutation + } +} diff --git a/src/codegen/node.rs b/src/codegen/node.rs index 2c33449..341a5ca 100644 --- a/src/codegen/node.rs +++ b/src/codegen/node.rs @@ -1,8 +1,8 @@ -use std::io::{self, Seek, Write}; +use std::fmt::{self, Display}; -use super::{node_id::NodeID, Sb3}; +use super::node_id::NodeID; -#[derive(Default, Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub struct Node { opcode: &'static str, this_id: NodeID, @@ -24,65 +24,53 @@ impl Node { } } - pub fn next_id(mut self, next_id: NodeID) -> Self { - self.next_id = Some(next_id); - self - } - - pub fn some_next_id(mut self, next_id: Option) -> Self { - self.next_id = next_id; - self + pub fn parent_id(self, parent_id: NodeID) -> Self { + Self { + parent_id: Some(parent_id), + ..self + } } - pub fn parent_id(mut self, parent_id: NodeID) -> Self { - self.parent_id = Some(parent_id); - self + pub fn top_level(self, top_level: bool) -> Self { + Self { top_level, ..self } } - pub fn some_parent_id(mut self, parent_id: Option) -> Self { - self.parent_id = parent_id; - self + pub fn shadow(self, shadow: bool) -> Self { + Self { shadow, ..self } } - pub fn top_level(mut self, top_level: bool) -> Self { - self.top_level = top_level; - self + pub fn some_next_id(self, next_id: Option) -> Self { + Self { next_id, ..self } } - pub fn shadow(mut self, shadow: bool) -> Self { - self.shadow = shadow; - self + pub fn some_parent_id(self, parent_id: Option) -> Self { + Self { parent_id, ..self } } } -impl Sb3 -where T: Write + Seek -{ - pub fn node(&mut self, node: Node) -> io::Result<()> { - if self.blocks_comma { - self.write_all(b",")?; - } - self.blocks_comma = true; - write!(self, r#"{}:{{"opcode":"{}""#, node.this_id, node.opcode)?; - if let Some(next_id) = node.next_id { - write!(self, r#","next":{next_id}"#)?; +impl Display for Node { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{{", self.this_id)?; + write!(f, "\"opcode\":\"{}\"", self.opcode)?; + if let Some(next_id) = self.next_id { + write!(f, ",\"next\":{next_id}")?; } else { - write!(self, r#","next":null"#)?; + write!(f, ",\"next\":null")?; } - if let Some(parent_id) = node.parent_id { - write!(self, r#","parent":{parent_id}"#)?; + if let Some(parent_id) = self.parent_id { + write!(f, ",\"parent\":{parent_id}")?; } else { - write!(self, r#","parent":null"#)?; + write!(f, ",\"parent\":null")?; } - if node.top_level { - self.write_all(br#","topLevel":true"#)?; + if self.top_level { + write!(f, ",\"topLevel\":true")?; } else { - self.write_all(br#","topLevel":false"#)?; + write!(f, ",\"topLevel\":false")?; } - if node.shadow { - self.write_all(br#","shadow":true"#)?; + if self.shadow { + write!(f, ",\"shadow\":true")?; } else { - self.write_all(br#","shadow":false"#)?; + write!(f, ",\"shadow\":false")?; } Ok(()) } diff --git a/src/codegen/node_id.rs b/src/codegen/node_id.rs index 181de12..36c49da 100644 --- a/src/codegen/node_id.rs +++ b/src/codegen/node_id.rs @@ -1,55 +1,18 @@ -use std::fmt::{self, Display, Formatter}; +use std::fmt::{self, Display}; -const BLOCK_ID_CHARS: &str = "abcdefghijklmnopqrstuvwxyz"; - -#[derive(Debug, Default, Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub struct NodeID { value: usize, } -impl Display for NodeID { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "\"{}\"", self.value) - // if self.value == 0 { - // return write!(f, r#""{}""#, BLOCK_ID_CHARS.chars().next().unwrap()); - // } - // let mut n = self.value; - // let len = BLOCK_ID_CHARS.chars().count(); - // let mut chars = Vec::new(); - // while n > 0 { - // chars.push(BLOCK_ID_CHARS.chars().nth(n % len).unwrap()); - // n /= len; - // } - // write!(f, "\"")?; - // for ch in chars.iter().rev() { - // write!(f, "{}", ch)?; - // } - // write!(f, "\"")?; - // Ok(()) +impl NodeID { + pub fn new(value: usize) -> Self { + Self { value } } } -#[derive(Debug, Default)] -pub struct NodeIDFactory { - state: NodeID, -} - -impl NodeIDFactory { - pub fn reset(&mut self) { - self.state.value = 0; - } - - pub fn new_id(&mut self) -> NodeID { - let id = self.state; - self.state.value += 1; - id - } -} - -impl Iterator for NodeIDFactory { - type Item = NodeID; - - fn next(&mut self) -> Option { - Some(self.new_id()) +impl Display for NodeID { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\"{}\"", self.value) } } diff --git a/src/codegen/node_id_factory.rs b/src/codegen/node_id_factory.rs new file mode 100644 index 0000000..71c4daa --- /dev/null +++ b/src/codegen/node_id_factory.rs @@ -0,0 +1,30 @@ +use super::node_id::NodeID; + +#[derive(Debug)] +pub struct NodeIDFactory { + value: usize, +} + +impl NodeIDFactory { + pub fn new() -> Self { + Self { value: 0 } + } + + pub fn reset(&mut self) { + self.value = 0; + } + + pub fn new_id(&mut self) -> NodeID { + let value = self.value; + self.value += 1; + NodeID::new(value) + } +} + +impl Iterator for NodeIDFactory { + type Item = NodeID; + + fn next(&mut self) -> Option { + Some(self.new_id()) + } +} diff --git a/src/codegen/sb3.rs b/src/codegen/sb3.rs new file mode 100644 index 0000000..1b0c1f7 --- /dev/null +++ b/src/codegen/sb3.rs @@ -0,0 +1,817 @@ +use core::str; +use std::{ + fs::File, + io::{self, Seek, Write}, + path::Path, + process::Command, +}; + +use fxhash::FxHashMap; +use logos::Span; +use md5::{Digest, Md5}; +use serde_json::json; +use smol_str::SmolStr; +use zip::{write::SimpleFileOptions, ZipWriter}; + +use super::{ + node::Node, node_id::NodeID, node_id_factory::NodeIDFactory, turbowarp_config::TurbowarpConfig, +}; +use crate::{ + ast::*, + blocks::Block, + codegen::mutation::Mutation, + config::Config, + diagnostic::{DiagnosticKind, SpriteDiagnostics}, + misc::write_comma_io, +}; + +const STAGE_NAME: &str = "Stage"; + +#[derive(Debug, Copy, Clone)] +pub struct S<'a> { + pub stage: Option<&'a Sprite>, + pub sprite: &'a Sprite, + pub proc: Option<&'a Proc>, +} + +pub type D<'a> = &'a mut SpriteDiagnostics; + +pub enum QualifiedName { + Var(SmolStr, Type), + List(SmolStr, Type), +} + +pub fn qualify_local_var_name(proc_name: &str, var_name: &str) -> SmolStr { + format!("{}:{}", proc_name, var_name).into() +} + +pub fn qualify_struct_var_name(field_name: &str, var_name: &str) -> SmolStr { + format!("{}.{}", var_name, field_name).into() +} + +impl<'a> S<'a> { + pub fn is_name_list(&self, name: &Name) -> bool { + self.sprite.lists.contains_key(name.basename()) + || self + .stage + .is_some_and(|stage| stage.lists.contains_key(name.basename())) + } + + fn get_local_var(&self, name: &str) -> Option<&Var> { + self.proc.and_then(|proc| proc.locals.get(name)) + } + + fn get_var(&self, name: &str) -> Option<&Var> { + self.sprite + .vars + .get(name) + .or_else(|| self.stage.and_then(|stage| stage.vars.get(name))) + } + + fn get_list(&self, name: &str) -> Option<&List> { + self.sprite + .lists + .get(name) + .or_else(|| self.stage.and_then(|stage| stage.lists.get(name))) + } + + fn get_struct(&self, name: &str) -> Option<&Struct> { + self.sprite + .structs + .get(name) + .or_else(|| self.stage.and_then(|stage| stage.structs.get(name))) + } + + fn qualify_field( + &self, + d: D, + span: &Span, + qualified_var_name: SmolStr, + field_name: Option, + type_: &Type, + variant: T, + ) -> Option + where + T: FnOnce(SmolStr, Type) -> QualifiedName, + { + match type_ { + Type::Value => match field_name { + None => Some(variant(qualified_var_name, type_.clone())), + Some(_) => { + d.report(DiagnosticKind::NotStruct, span); + None + } + }, + Type::Struct { + name: type_name, + span: type_span, + } => match field_name { + None => panic!("attempted to qualify struct var without field name, type error?"), + Some(field_name) => { + let struct_ = self.get_struct(type_name)?; + if !struct_.fields.iter().any(|field| field.name == field_name) { + d.report( + DiagnosticKind::StructDoesNotHaveField { + type_name: type_name.clone(), + field_name: field_name.clone(), + }, + type_span, + ); + None + } else { + Some(variant( + qualify_struct_var_name(&field_name, &qualified_var_name), + type_.clone(), + )) + } + } + }, + } + } + + pub fn qualify_name(&self, d: D, name: &Name) -> Option { + let basename = name.basename(); + let fieldname = name.fieldname().cloned(); + if let Some(list) = self.get_list(basename) { + return self.qualify_field( + d, + &name.span(), + list.name.clone(), + fieldname, + &list.type_, + QualifiedName::List, + ); + } + if let Some(var) = self.get_local_var(basename) { + let qualified_var_name = qualify_local_var_name(&self.proc.unwrap().name, &var.name); + return self.qualify_field( + d, + &name.span(), + qualified_var_name, + fieldname, + &var.type_, + QualifiedName::Var, + ); + } + if let Some(var) = self.get_var(basename) { + return self.qualify_field( + d, + &name.span(), + var.name.clone(), + fieldname, + &var.type_, + QualifiedName::Var, + ); + } + d.report( + DiagnosticKind::UnrecognizedVariable(basename.clone()), + &name.span(), + ); + None + } +} + +impl Stmt { + fn is_terminator(&self) -> bool { + matches!( + self, + Stmt::Forever { .. } + | Stmt::Block { + block: Block::DeleteThisClone | Block::StopAll | Block::StopThisScript, + .. + } + ) + } + fn opcode(&self, s: S) -> &'static str { + match self { + Stmt::Repeat { .. } => "control_repeat", + Stmt::Forever { .. } => "control_forever", + Stmt::Branch { else_body, .. } => { + if else_body.is_empty() { + "control_if" + } else { + "control_if_else" + } + } + Stmt::Until { .. } => "control_repeat_until", + Stmt::SetVar { .. } => "data_setvariableto", + Stmt::Show(name) => { + if s.is_name_list(name) { + "data_showlist" + } else { + "data_showvariable" + } + } + Stmt::Hide(name) => { + if s.is_name_list(name) { + "data_hidelist" + } else { + "data_hidevariable" + } + } + Stmt::AddToList { .. } => "data_addtolist", + Stmt::DeleteListIndex { .. } => "data_deleteoflist", + Stmt::DeleteList { .. } => "data_deletealloflist", + Stmt::InsertAtList { .. } => "data_insertatlist", + Stmt::SetListIndex { .. } => "data_replaceitemoflist", + Stmt::Block { block, .. } => block.opcode(), + Stmt::ProcCall { .. } => "procedures_call", + } + } +} + +#[derive(Debug)] +pub struct Sb3 +where T: Write + Seek +{ + pub zip: ZipWriter, + pub id: NodeIDFactory, + pub node_comma: bool, + pub inputs_comma: bool, + pub costumes: FxHashMap, +} + +impl Write for Sb3 +where T: Write + Seek +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + self.zip.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.zip.flush() + } +} + +impl Sb3 +where T: Write + Seek +{ + pub fn new(file: T) -> Self { + Self { + zip: ZipWriter::new(file), + id: NodeIDFactory::new(), + node_comma: false, + inputs_comma: false, + costumes: FxHashMap::default(), + } + } + + pub fn begin_node(&mut self, node: Node) -> io::Result<()> { + write_comma_io(&mut self.zip, &mut self.node_comma)?; + write!(self, "{node}") + } + + pub fn end_obj(&mut self) -> io::Result<()> { + self.write_all(b"}") + } + + pub fn begin_inputs(&mut self) -> io::Result<()> { + self.inputs_comma = false; + self.write_all(br#","inputs":{"#) + } + + pub fn single_field(&mut self, name: &'static str, value: &str) -> io::Result<()> { + write!(self, r#","fields":{{"{name}":[{},null]}}"#, json!(value)) + } + + pub fn single_field_id(&mut self, name: &'static str, value: &str) -> io::Result<()> { + write!( + self, + r#","fields":{{"{name}":[{},{}]}}"#, + json!(value), + json!(value) + ) + } + + pub fn substack(&mut self, name: &str, this_id: Option) -> io::Result<()> { + let Some(this_id) = this_id else { + return Ok(()); + }; + write_comma_io(&mut self.zip, &mut self.inputs_comma)?; + write!(self, r#""{name}":[2,{this_id}]"#) + } + + pub fn project( + &mut self, + input: &Path, + project: &Project, + config: &Config, + stage_diagnostics: D, + sprites_diagnostics: &mut FxHashMap, + ) -> io::Result<()> { + // TODO: switch to deflate compression + // this should be configurable, use store in debug (because it would be + // faster?), use deflate in release (because it would be smaller?) + self.zip + .start_file("project.json", SimpleFileOptions::default())?; + write!(self, "{{")?; + write!(self, r#""targets":["#)?; + self.sprite( + input, + STAGE_NAME, + &project.stage, + None, + config, + stage_diagnostics, + )?; + for (sprite_name, sprite) in &project.sprites { + write!(self, r#","#)?; + self.sprite( + input, + sprite_name, + sprite, + Some(&project.stage), + config, + sprites_diagnostics.get_mut(sprite_name).unwrap(), + )?; + } + write!(self, "]")?; // targets + write!(self, r#","monitors":[]"#)?; + write!(self, r#","extensions":[]"#)?; + write!(self, r#","meta":{{"#)?; + write!(self, r#""semver":"3.0.0""#)?; + write!(self, r#","vm":"0.2.0""#)?; + write!( + self, + r#","agent":"goboscript v{}""#, + env!("CARGO_PKG_VERSION") + )?; + write!(self, "}}")?; // meta + write!(self, "}}")?; // project + Ok(()) + } + + pub fn sprite( + &mut self, + input: &Path, + name: &str, + sprite: &Sprite, + stage: Option<&Sprite>, + config: &Config, + d: D, + ) -> io::Result<()> { + self.id.reset(); + write!(self, "{{")?; + write!(self, r#""isStage":{}"#, name == STAGE_NAME)?; + write!(self, r#","name":{}"#, json!(name))?; + if name == STAGE_NAME { + write!(self, r#","comments":{{"#)?; + write!(self, r#""twconfig":{{"#)?; + write!(self, r#""blockId":null"#)?; + write!(self, r#","x":0"#)?; + write!(self, r#","y":0"#)?; + write!(self, r#","width":350"#)?; + write!(self, r#","height":170"#)?; + write!(self, r#","minimized":false"#)?; + write!( + self, + r#","text":{}"#, + json!(TurbowarpConfig::from(config).to_string()) + )?; + write!(self, "}}")?; // twconfig + write!(self, "}}")?; // comments + } + write!(self, r#","variables":{{"#)?; + let mut comma = false; + for proc in sprite.procs.values() { + for var in proc.locals.values() { + self.local_var_declaration(sprite, proc, var, &mut comma, d)?; + } + } + for var in sprite.vars.values() { + self.var_declaration(sprite, var, &mut comma, d)?; + } + write!(self, "}}")?; // variables + write!(self, r#","lists":{{"#)?; + let mut comma = false; + for list in sprite.lists.values() { + self.list_declaration(input, sprite, list, &mut comma, d)?; + } + write!(self, "}}")?; // lists + write!(self, r#","blocks":{{"#)?; + self.node_comma = false; + for proc in sprite.procs.values() { + self.proc( + S { + stage, + sprite, + proc: Some(proc), + }, + d, + proc, + )?; + } + for event in &sprite.events { + self.event( + S { + stage, + sprite, + proc: None, + }, + d, + event, + )?; + } + write!(self, "}}")?; // blocks + write!(self, r#","costumes":["#)?; + let mut comma = false; + for costume in &sprite.costumes { + write_comma_io(&mut self.zip, &mut comma)?; + self.costume(input, costume, d)?; + } + write!(self, "]")?; // costumes + write!(self, r#","sounds":["#)?; + write!(self, "]")?; // sounds + write!(self, "}}")?; // sprite + Ok(()) + } + + pub fn json_var_declaration(&mut self, var_name: &str, comma: &mut bool) -> io::Result<()> { + write_comma_io(&mut self.zip, comma)?; + write!(self, r#""{}":["{}",0]"#, var_name, var_name) + } + + pub fn var_declaration( + &mut self, + sprite: &Sprite, + var: &Var, + comma: &mut bool, + d: D, + ) -> io::Result<()> { + match &var.type_ { + Type::Value => { + self.json_var_declaration(&var.name, comma)?; + } + Type::Struct { + name: type_name, + span: type_span, + } => { + let Some(struct_) = sprite.structs.get(type_name) else { + d.report( + DiagnosticKind::UnrecognizedStruct(type_name.clone()), + type_span, + ); + return Ok(()); + }; + for field in &struct_.fields { + let qualified_var_name = qualify_struct_var_name(&field.name, &var.name); + self.json_var_declaration(&qualified_var_name, comma)?; + } + } + } + Ok(()) + } + + pub fn local_var_declaration( + &mut self, + sprite: &Sprite, + proc: &Proc, + var: &Var, + comma: &mut bool, + d: D, + ) -> io::Result<()> { + match &var.type_ { + Type::Value => { + let qualified_var_name = qualify_local_var_name(&proc.name, &var.name); + self.json_var_declaration(&qualified_var_name, comma)?; + } + Type::Struct { + name: type_name, + span: type_span, + } => { + let Some(struct_) = sprite.structs.get(type_name) else { + d.report( + DiagnosticKind::UnrecognizedStruct(type_name.clone()), + type_span, + ); + return Ok(()); + }; + for field in &struct_.fields { + let qualified_var_name = qualify_local_var_name( + &proc.name, + &qualify_struct_var_name(&field.name, &var.name), + ); + self.json_var_declaration(&qualified_var_name, comma)?; + } + } + } + Ok(()) + } + + pub fn list_declaration( + &mut self, + input: &Path, + sprite: &Sprite, + list: &List, + comma: &mut bool, + d: D, + ) -> io::Result<()> { + let data = match &list.cmd { + Some(cmd) => { + eprintln!("{cmd:#?}"); + let output = Command::new("/usr/bin/sh") + .arg("-c") + .arg(format!("cd {};{}", input.display(), cmd.cmd)) + .output()?; + if !output.status.success() { + d.report( + DiagnosticKind::CommandFailed { + stderr: output.stderr, + }, + &cmd.span, + ); + } + output.status.success().then(|| { + let mut lines = output + .stdout + .split(|&b| b == b'\n') + .map(|line| str::from_utf8(line).unwrap_or_default().to_owned()) + .collect::>(); + lines.pop(); + lines + }) + } + None => None, + }; + match &list.type_ { + Type::Value => { + write_comma_io(&mut self.zip, comma)?; + if let Some(cmd) = data { + write!(self, r#""{}":["{}",{}]"#, list.name, list.name, json!(cmd))?; + } else { + write!(self, r#""{}":["{}",[]]"#, list.name, list.name)?; + } + } + Type::Struct { + name: type_name, + span: type_span, + } => { + let Some(struct_) = sprite.structs.get(type_name) else { + d.report( + DiagnosticKind::UnrecognizedStruct(type_name.clone()), + type_span, + ); + return Ok(()); + }; + for (i, field) in struct_.fields.iter().enumerate() { + let qualified_list_name = qualify_struct_var_name(&field.name, &list.name); + write_comma_io(&mut self.zip, comma)?; + if let Some(cmd) = &data { + let column = (0..(cmd.len() / struct_.fields.len())) + .map(|j| &cmd[j * struct_.fields.len() + i]) + .collect::>(); + write!( + self, + r#""{}":["{}",{}]"#, + qualified_list_name, + qualified_list_name, + json!(column) + )?; + } else { + write!( + self, + r#""{}":["{}",[]]"#, + qualified_list_name, qualified_list_name + )?; + } + } + } + } + Ok(()) + } + + pub fn costume(&mut self, input: &Path, costume: &Costume, d: D) -> io::Result<()> { + let path = input.join(&costume.path); + let hash = self + .costumes + .get(&costume.path) + .cloned() + .map(Ok::<_, io::Error>) + .unwrap_or_else(|| { + if !path.is_file() { + d.report( + DiagnosticKind::FileNotFound(costume.path.clone()), + &costume.span, + ); + return Ok(Default::default()); + } + let mut file = File::open(&path)?; + let mut hasher = Md5::new(); + io::copy(&mut file, &mut hasher)?; + let hash: SmolStr = format!("{:x}", hasher.finalize()).into(); + self.costumes.insert(costume.path.clone(), hash.clone()); + Ok(hash) + })?; + let (_, extension) = costume.path.rsplit_once('.').unwrap(); + write!(self, "{{")?; + write!(self, r#""name":{}"#, json!(*costume.name))?; + write!(self, r#","assetId":"{hash}""#)?; + write!(self, r#","dataFormat":"{extension}""#)?; + write!(self, r#","md5ext":"{hash}.{extension}""#)?; + write!(self, "}}") // costume + } + + pub fn proc(&mut self, s: S, d: D, proc: &Proc) -> io::Result<()> { + let this_id = self.id.new_id(); + let prototype_id = self.id.new_id(); + let next_id = self.id.new_id(); + self.begin_node( + Node::new("procedures_definition", this_id) + .some_next_id((!proc.body.is_empty()).then_some(next_id)) + .top_level(true), + )?; + self.begin_inputs()?; + write!(self, r#""custom_block":[1,{prototype_id}]"#)?; + self.end_obj()?; // inputs + self.end_obj()?; // node + let mut qualified_args: Vec<(SmolStr, NodeID)> = Vec::new(); + for arg in &proc.args { + match &arg.type_ { + Type::Value => { + let arg_id = self.id.new_id(); + self.begin_node( + Node::new("argument_reporter_string_number", arg_id) + .parent_id(prototype_id) + .shadow(true), + )?; + self.single_field("VALUE", &arg.name)?; + self.end_obj()?; // node + qualified_args.push((arg.name.clone(), arg_id)); + } + Type::Struct { + name: type_name, + span: type_span, + } => { + let Some(struct_) = s.sprite.structs.get(type_name) else { + d.report( + DiagnosticKind::UnrecognizedStruct(type_name.clone()), + type_span, + ); + continue; + }; + for field in &struct_.fields { + let qualified_arg_name = qualify_struct_var_name(&field.name, &arg.name); + let arg_id = self.id.new_id(); + self.begin_node( + Node::new("argument_reporter_string_number", arg_id) + .parent_id(prototype_id) + .shadow(true), + )?; + self.single_field("VALUE", &qualified_arg_name)?; + self.end_obj()?; // node + qualified_args.push((qualified_arg_name, arg_id)); + } + } + } + } + self.begin_node( + Node::new("procedures_prototype", prototype_id) + .parent_id(this_id) + .shadow(true), + )?; + self.begin_inputs()?; + let mut comma = false; + for (qualified_arg_name, arg_id) in &qualified_args { + write_comma_io(&mut self.zip, &mut comma)?; + write!(self, r#"{}:[2,{arg_id}]"#, json!(**qualified_arg_name))?; + } + self.end_obj()?; // inputs + write!( + self, + "{}", + Mutation::prototype(proc.name.clone(), &qualified_args, proc.warp,) + )?; + self.end_obj()?; // node + self.stmts(s, d, &proc.body, next_id, Some(this_id)) + } + + pub fn event(&mut self, s: S, d: D, event: &Event) -> io::Result<()> { + let this_id = self.id.new_id(); + let next_id = self.id.new_id(); + self.begin_node( + Node::new(event.kind.opcode(), this_id) + .some_next_id((!event.body.is_empty()).then_some(next_id)) + .top_level(true), + )?; + match &event.kind { + EventKind::OnFlag => self.on_flag(s, d, this_id), + EventKind::OnKey { key, span } => self.on_key(s, d, this_id, key, span), + EventKind::OnClick => self.on_click(s, d, this_id), + EventKind::OnBackdrop { backdrop, span } => { + self.on_backdrop(s, d, this_id, backdrop, span) + } + EventKind::OnLoudnessGt { value } => self.on_loudness_gt(s, d, this_id, value), + EventKind::OnTimerGt { value } => self.on_timer_gt(s, d, this_id, value), + EventKind::OnClone => self.on_clone(s, d, this_id), + }?; + self.stmts(s, d, &event.body, next_id, Some(this_id)) + } + + pub fn stmts( + &mut self, + s: S, + d: D, + stmts: &[Stmt], + mut this_id: NodeID, + mut parent_id: Option, + ) -> io::Result<()> { + for (i, stmt) in stmts.iter().enumerate() { + let is_last = i == stmts.len() - 1; + if is_last || stmt.is_terminator() { + self.stmt(s, d, stmt, this_id, None, parent_id)?; + if !is_last { + d.report(DiagnosticKind::FollowedByUnreachableCode, stmt.span()); + } + break; + } + let next_id = self.id.new_id(); + self.stmt(s, d, stmt, this_id, Some(next_id), parent_id)?; + parent_id = Some(this_id); + this_id = next_id; + } + Ok(()) + } + + pub fn stmt( + &mut self, + s: S, + d: D, + stmt: &Stmt, + this_id: NodeID, + next_id: Option, + parent_id: Option, + ) -> io::Result<()> { + self.begin_node( + Node::new(stmt.opcode(s), this_id) + .some_next_id(next_id) + .some_parent_id(parent_id), + )?; + match stmt { + Stmt::Repeat { times, body } => self.repeat(s, d, this_id, times, body), + Stmt::Forever { body, span } => self.forever(s, d, this_id, body, span), + Stmt::Branch { + cond, + if_body, + else_body, + } => self.branch(s, d, this_id, cond, if_body, else_body), + Stmt::Until { cond, body } => self.until(s, d, this_id, cond, body), + Stmt::SetVar { + name, + value, + type_, + is_local, + } => self.set_var(s, d, this_id, name, value, type_, is_local), + Stmt::Show(name) => self.show(s, d, name), + Stmt::Hide(name) => self.hide(s, d, name), + Stmt::AddToList { name, value } => self.add_to_list(s, d, this_id, name, value), + Stmt::DeleteListIndex { name, index } => { + self.delete_list_index(s, d, this_id, name, index) + } + Stmt::DeleteList(name) => self.delete_list(s, d, name), + Stmt::InsertAtList { name, index, value } => { + self.list_insert(s, d, this_id, name, index, value) + } + Stmt::SetListIndex { name, index, value } => { + self.set_list_index(s, d, this_id, name, index, value) + } + Stmt::Block { block, span, args } => self.block(s, d, this_id, block, span, args), + Stmt::ProcCall { name, span, args } => self.proc_call(s, d, this_id, name, span, args), + } + } + + pub fn expr( + &mut self, + s: S, + d: D, + expr: &Expr, + this_id: NodeID, + parent_id: NodeID, + ) -> io::Result<()> { + match expr { + Expr::Value { .. } => Ok(()), + Expr::Name { .. } => Ok(()), + Expr::Arg(name) => self.arg(s, d, this_id, parent_id, name), + Expr::Repr { repr, span, args } => { + self.repr(s, d, this_id, parent_id, repr, span, args) + } + Expr::UnOp { op, span, opr } => self.un_op(s, d, this_id, parent_id, op, span, opr), + Expr::BinOp { op, span, lhs, rhs } => { + self.bin_op(s, d, this_id, parent_id, op, span, lhs, rhs) + } + Expr::StructLiteral { name, span, .. } => { + d.report( + DiagnosticKind::TypeMismatch { + expected: Type::Value, + given: Type::Struct { + name: name.clone(), + span: span.clone(), + }, + }, + &expr.span(), + ); + Ok(()) + } + Expr::Dot { .. } => panic!("Attempted to codegen {expr:#?}"), + } + } +} diff --git a/src/codegen/stmt.rs b/src/codegen/stmt.rs new file mode 100644 index 0000000..9736d2f --- /dev/null +++ b/src/codegen/stmt.rs @@ -0,0 +1,448 @@ +use std::io::{self, Seek, Write}; + +use logos::Span; +use smol_str::SmolStr; + +use super::{ + node::Node, + node_id::NodeID, + sb3::{qualify_struct_var_name, QualifiedName, Sb3, D, S}, +}; +use crate::{ + ast::{Expr, Name, Stmt, Type}, + blocks::Block, + codegen::mutation::Mutation, + diagnostic::DiagnosticKind, + misc::{write_comma_io, Rrc}, +}; + +impl Sb3 +where T: Write + Seek +{ + pub fn repeat( + &mut self, + s: S, + d: D, + this_id: NodeID, + times: &Rrc, + body: &[Stmt], + ) -> io::Result<()> { + let times_id = self.id.new_id(); + let body_id = self.id.new_id(); + self.begin_inputs()?; + self.input(s, d, "TIMES", ×.borrow(), times_id)?; + self.substack("SUBSTACK", (!body.is_empty()).then_some(body_id))?; + self.end_obj()?; // inputs + self.end_obj()?; // node + self.expr(s, d, ×.borrow(), times_id, this_id)?; + self.stmts(s, d, body, body_id, Some(this_id)) + } + + pub fn forever( + &mut self, + s: S, + d: D, + this_id: NodeID, + body: &[Stmt], + _span: &Span, + ) -> io::Result<()> { + let body_id = self.id.new_id(); + self.begin_inputs()?; + self.substack("SUBSTACK", (!body.is_empty()).then_some(body_id))?; + self.end_obj()?; // inputs + self.end_obj()?; // node + self.stmts(s, d, body, body_id, Some(this_id)) + } + + pub fn branch( + &mut self, + s: S, + d: D, + this_id: NodeID, + cond: &Rrc, + if_body: &[Stmt], + else_body: &[Stmt], + ) -> io::Result<()> { + let cond_id = self.id.new_id(); + let if_body_id = self.id.new_id(); + let else_body_id = self.id.new_id(); + self.begin_inputs()?; + self.input(s, d, "CONDITION", &cond.borrow(), cond_id)?; + self.substack("SUBSTACK", (!if_body.is_empty()).then_some(if_body_id))?; + self.substack("SUBSTACK2", (!else_body.is_empty()).then_some(else_body_id))?; + self.end_obj()?; // inputs + self.end_obj()?; // node + self.expr(s, d, &cond.borrow(), cond_id, this_id)?; + self.stmts(s, d, if_body, if_body_id, Some(this_id))?; + self.stmts(s, d, else_body, else_body_id, Some(this_id)) + } + + pub fn until( + &mut self, + s: S, + d: D, + this_id: NodeID, + cond: &Rrc, + body: &[Stmt], + ) -> io::Result<()> { + let cond_id = self.id.new_id(); + let body_id = self.id.new_id(); + self.begin_inputs()?; + self.input(s, d, "CONDITION", &cond.borrow(), cond_id)?; + self.substack("SUBSTACK", (!body.is_empty()).then_some(body_id))?; + self.end_obj()?; // inputs + self.end_obj()?; // node + self.expr(s, d, &cond.borrow(), cond_id, this_id)?; + self.stmts(s, d, body, body_id, Some(this_id)) + } + + pub fn set_var( + &mut self, + s: S, + d: D, + this_id: NodeID, + name: &Name, + value: &Rrc, + _type: &Type, + _is_local: &bool, + ) -> io::Result<()> { + let value_id = self.id.new_id(); + self.begin_inputs()?; + self.input(s, d, "VALUE", &value.borrow(), value_id)?; + self.end_obj()?; // inputs + match s.qualify_name(d, name) { + Some(QualifiedName::Var(qualified_name, _)) => { + self.single_field_id("VARIABLE", &qualified_name)? + } + Some(QualifiedName::List(..)) => { + d.report( + DiagnosticKind::UnrecognizedVariable(name.basename().clone()), + &name.span(), + ); + } + None => {} + } + self.end_obj()?; // node + self.expr(s, d, &value.borrow(), value_id, this_id) + } + + pub fn show(&mut self, s: S, d: D, name: &Name) -> io::Result<()> { + self.begin_inputs()?; + self.end_obj()?; // inputs + match s.qualify_name(d, name) { + Some(QualifiedName::Var(qualified_name, _)) => { + self.single_field_id("VARIABLE", &qualified_name)? + } + Some(QualifiedName::List(qualified_name, _)) => { + self.single_field_id("LIST", &qualified_name)? + } + None => {} + } + self.end_obj() // node + } + + pub fn hide(&mut self, s: S, d: D, name: &Name) -> io::Result<()> { + self.show(s, d, name) + } + + pub fn add_to_list( + &mut self, + s: S, + d: D, + this_id: NodeID, + name: &Name, + value: &Rrc, + ) -> io::Result<()> { + let value_id = self.id.new_id(); + self.begin_inputs()?; + self.input(s, d, "ITEM", &value.borrow(), value_id)?; + self.end_obj()?; // inputs + match s.qualify_name(d, name) { + Some(QualifiedName::List(qualified_name, _)) => { + self.single_field_id("LIST", &qualified_name)? + } + Some(QualifiedName::Var(..)) => { + d.report( + DiagnosticKind::UnrecognizedList(name.basename().clone()), + &name.span(), + ); + } + None => {} + } + self.end_obj()?; // node + self.expr(s, d, &value.borrow(), value_id, this_id) + } + + pub fn delete_list_index( + &mut self, + s: S, + d: D, + this_id: NodeID, + name: &Name, + index: &Rrc, + ) -> io::Result<()> { + let index_id = self.id.new_id(); + self.begin_inputs()?; + self.input(s, d, "INDEX", &index.borrow(), index_id)?; + self.end_obj()?; // inputs + match s.qualify_name(d, name) { + Some(QualifiedName::List(qualified_name, _)) => { + self.single_field_id("LIST", &qualified_name)? + } + Some(QualifiedName::Var(..)) => { + d.report( + DiagnosticKind::UnrecognizedList(name.basename().clone()), + &name.span(), + ); + } + None => {} + } + self.end_obj()?; // node + self.expr(s, d, &index.borrow(), index_id, this_id) + } + + pub fn delete_list(&mut self, s: S, d: D, name: &Name) -> io::Result<()> { + self.begin_inputs()?; + self.end_obj()?; // inputs + match s.qualify_name(d, name) { + Some(QualifiedName::List(qualified_name, _)) => { + self.single_field_id("LIST", &qualified_name)? + } + Some(QualifiedName::Var(..)) => { + d.report( + DiagnosticKind::UnrecognizedList(name.basename().clone()), + &name.span(), + ); + } + None => {} + } + self.end_obj() // node + } + + pub fn list_insert( + &mut self, + s: S, + d: D, + this_id: NodeID, + name: &Name, + index: &Rrc, + value: &Rrc, + ) -> io::Result<()> { + let index_id = self.id.new_id(); + let value_id = self.id.new_id(); + self.begin_inputs()?; + self.input(s, d, "INDEX", &index.borrow(), index_id)?; + self.input(s, d, "ITEM", &value.borrow(), value_id)?; + self.end_obj()?; // inputs + match s.qualify_name(d, name) { + Some(QualifiedName::List(qualified_name, _)) => { + self.single_field_id("LIST", &qualified_name)? + } + Some(QualifiedName::Var(..)) => { + d.report( + DiagnosticKind::UnrecognizedList(name.basename().clone()), + &name.span(), + ); + } + None => {} + } + self.end_obj()?; // node + self.expr(s, d, &index.borrow(), index_id, this_id)?; + self.expr(s, d, &value.borrow(), value_id, this_id) + } + + pub fn set_list_index( + &mut self, + s: S, + d: D, + this_id: NodeID, + name: &Name, + index: &Rrc, + value: &Rrc, + ) -> io::Result<()> { + self.list_insert(s, d, this_id, name, index, value) + } + + pub fn block( + &mut self, + s: S, + d: D, + this_id: NodeID, + block: &Block, + span: &Span, + args: &Vec>, + ) -> io::Result<()> { + if block.args().len() != args.len() { + d.report( + DiagnosticKind::BlockArgsCountMismatch { + block: *block, + given: args.len(), + }, + span, + ) + } + self.begin_inputs()?; + let arg_ids: Vec = (&mut self.id).take(args.len()).collect(); + let menu_id = block.menu().map(|_| self.id.new_id()); + let mut menu_value = None; + let mut menu_is_default = menu_id.is_some(); + for ((&arg_name, arg_value), &arg_id) in block.args().iter().zip(args).zip(&arg_ids) { + if block.menu().is_some_and(|menu| menu.input == arg_name) { + if let Expr::Value { value, span: _ } = &*arg_value.borrow() { + menu_value = Some(value.clone()); + continue; + } else { + menu_is_default = false; + self.input_with_shadow( + s, + d, + arg_name, + &arg_value.borrow(), + arg_id, + menu_id.unwrap(), + )?; + } + } else { + self.input(s, d, arg_name, &arg_value.borrow(), arg_id)?; + } + } + if menu_is_default { + write_comma_io(&mut self.zip, &mut self.inputs_comma)?; + write!( + self, + r#""{}":[1,{}]"#, + block.menu().unwrap().input, + menu_id.unwrap() + )?; + } + self.end_obj()?; // inputs + if let Some(fields) = block.fields() { + write!(self, r#","fields":{fields}"#)?; + } + self.end_obj()?; // node + for (arg, arg_id) in args.iter().zip(arg_ids) { + self.expr(s, d, &arg.borrow(), arg_id, this_id)?; + } + if let Some(menu) = block.menu() { + self.begin_node( + Node::new(menu.opcode, menu_id.unwrap()) + .parent_id(this_id) + .shadow(true), + )?; + if let Some(menu_value) = menu_value { + self.single_field(menu.input, &menu_value.to_string())?; + } else { + self.single_field(menu.input, menu.default)?; + } + self.end_obj()?; // node + } + Ok(()) + } + + pub fn proc_call( + &mut self, + s: S, + d: D, + this_id: NodeID, + name: &SmolStr, + span: &Span, + args: &Vec>, + ) -> io::Result<()> { + let Some(proc) = s.sprite.procs.get(name) else { + d.report(DiagnosticKind::UnrecognizedProcedure(name.clone()), span); + return Ok(()); + }; + if proc.args.len() != args.len() { + d.report( + DiagnosticKind::ProcArgsCountMismatch { + proc: name.clone(), + given: args.len(), + }, + span, + ) + } + let mut qualified_args: Vec<(SmolStr, NodeID)> = Vec::new(); + let mut qualified_arg_values: Vec> = Vec::new(); + self.begin_inputs()?; + for (arg, arg_value) in proc.args.iter().zip(args) { + match &arg.type_ { + Type::Value => { + let arg_id = self.id.new_id(); + self.input(s, d, &arg.name, &arg_value.borrow(), arg_id)?; + qualified_args.push((arg.name.clone(), arg_id)); + qualified_arg_values.push(arg_value.clone()); + } + Type::Struct { + name: type_name, + span: type_span, + } => { + let Some(struct_) = s.sprite.structs.get(type_name) else { + continue; + }; + let arg_value = &*arg_value.borrow(); + let struct_literal_fields = match arg_value { + Expr::StructLiteral { + name: struct_literal_name, + span: struct_literal_span, + fields: struct_literal_fields, + } => { + if struct_literal_name != &struct_.name { + d.report( + DiagnosticKind::TypeMismatch { + expected: arg.type_.clone(), + given: Type::Struct { + name: struct_literal_name.clone(), + span: struct_literal_span.clone(), + }, + }, + type_span, + ); + continue; + } + if struct_literal_fields.len() != struct_.fields.len() { + panic!() + } + for (struct_field, struct_literal_field) in + struct_.fields.iter().zip(struct_literal_fields) + { + if struct_field.name != struct_literal_field.name { + panic!() + } + } + struct_literal_fields + } + _ => { + continue; + } + }; + for (field, struct_literal_field) in + struct_.fields.iter().zip(struct_literal_fields) + { + let qualified_arg_name = qualify_struct_var_name(&field.name, &arg.name); + let arg_id = self.id.new_id(); + self.input( + s, + d, + &qualified_arg_name, + &struct_literal_field.value.borrow(), + arg_id, + )?; + qualified_args.push((qualified_arg_name, arg_id)); + qualified_arg_values.push(struct_literal_field.value.clone()); + } + } + } + } + self.end_obj()?; // inputs + write!( + self, + "{}", + Mutation::call(proc.name.clone(), &qualified_args, proc.warp) + )?; + self.end_obj()?; // node + for (arg, (_, arg_id)) in qualified_arg_values.iter().zip(qualified_args) { + self.expr(s, d, &arg.borrow(), arg_id, this_id)?; + } + Ok(()) + } +} diff --git a/src/codegen/turbowarp_config.rs b/src/codegen/turbowarp_config.rs new file mode 100644 index 0000000..ed82e30 --- /dev/null +++ b/src/codegen/turbowarp_config.rs @@ -0,0 +1,84 @@ +use std::fmt::{self, Display}; + +use crate::config::Config; + +#[derive(Debug)] +pub struct TurbowarpConfig { + pub frame_rate: u64, + pub max_clones: f64, + pub no_miscellaneous_limits: bool, + pub no_sprite_fencing: bool, + pub frame_interpolation: bool, + pub high_quality_pen: bool, + pub stage_width: u64, + pub stage_height: u64, +} + +impl Default for TurbowarpConfig { + fn default() -> Self { + Self { + frame_rate: 30, + max_clones: 300.0, + no_miscellaneous_limits: false, + no_sprite_fencing: false, + frame_interpolation: false, + high_quality_pen: false, + stage_width: 480, + stage_height: 360, + } + } +} + +#[allow(clippy::write_with_newline)] +impl Display for TurbowarpConfig { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Configuration for https://turbowarp.org/\n")?; + write!( + f, + "You can move, resize, and minimize this comment, but don't " + )?; + write!( + f, + "edit it by hand. This comment can be deleted to remove the " + )?; + write!(f, "stored settings.\n")?; + write!(f, "{{")?; + write!(f, r#""framerate":{}"#, self.frame_rate)?; + write!(f, r#","interpolation":{}"#, self.frame_interpolation)?; + write!(f, r#","hq":{}"#, self.high_quality_pen)?; + write!(f, r#","width":{}"#, self.stage_width)?; + write!(f, r#","height":{}"#, self.stage_height)?; + write!(f, r#","runtimeOptions":{{"#)?; + if self.max_clones.is_infinite() { + write!(f, r#""maxClones":Infinity"#)?; + } else { + write!(f, r#""maxClones":{}"#, self.max_clones)?; + } + write!(f, r#","miscLimits":{}"#, !self.no_miscellaneous_limits)?; + write!(f, r#","fencing":{}"#, !self.no_sprite_fencing)?; + write!(f, r#"}}"#)?; // runtimeOptions + write!(f, "}} // _twconfig_") // twconfig + } +} + +impl From<&Config> for TurbowarpConfig { + fn from(config: &Config) -> Self { + let default = Self::default(); + Self { + frame_rate: config.frame_rate.unwrap_or(default.frame_rate), + max_clones: config.max_clones.unwrap_or(default.max_clones), + no_miscellaneous_limits: config + .no_miscellaneous_limits + .unwrap_or(default.no_miscellaneous_limits), + no_sprite_fencing: config + .no_sprite_fencing + .unwrap_or(default.no_sprite_fencing), + frame_interpolation: config + .frame_interpolation + .unwrap_or(default.frame_interpolation), + high_quality_pen: config.high_quality_pen.unwrap_or(default.high_quality_pen), + stage_width: config.stage_width.unwrap_or(default.stage_width), + stage_height: config.stage_height.unwrap_or(default.stage_height), + } + } +} diff --git a/src/config.rs b/src/config.rs index e5490ee..07686bc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,3 @@ -use std::fmt::{self, Display, Formatter}; - use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Copy)] @@ -21,116 +19,3 @@ pub struct Config { #[serde(default)] pub stage_height: Option, } - -impl Config { - #[rustfmt::skip] - #[allow(clippy::bool_comparison)] - pub fn is_default(&self) -> bool { - !( self.frame_rate .is_some_and(|it| it != 30 ) - || self.max_clones .is_some_and(|it| it != 300.0) - || self.no_miscellaneous_limits.is_some_and(|it| it != false) - || self.no_sprite_fencing .is_some_and(|it| it != false) - || self.frame_interpolation .is_some_and(|it| it != false) - || self.high_quality_pen .is_some_and(|it| it != false) - || self.stage_width .is_some_and(|it| it != 480 ) - || self.stage_height .is_some_and(|it| it != 360 )) - } -} - -impl Display for Config { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut comma = false; - write!(f, "Configuration for https://turbowarp.org/\nYou can move, resize, and minimize this comment, but don't edit it by hand. This comment can be deleted to remove the stored settings.\n{{")?; - if let Some(frame_rate) = self.frame_rate { - if frame_rate != 30 { - write!(f, r#""framerate":{}"#, frame_rate)?; - comma = true; - } - } - if let Some(frame_interpolation) = self.frame_interpolation { - if frame_interpolation { - if comma { - write!(f, ",")?; - } - write!(f, r#""interpolation":true"#)?; - comma = true; - } - } - if let Some(high_quality_pen) = self.high_quality_pen { - if high_quality_pen { - if comma { - write!(f, ",")?; - } - write!(f, r#""hq":true"#)?; - comma = true; - } - } - if let Some(stage_width) = self.stage_width { - if stage_width != 480 { - if comma { - write!(f, ",")?; - } - write!(f, r#""width":{}"#, stage_width)?; - comma = true; - } - } - if let Some(stage_height) = self.stage_height { - if stage_height != 360 { - if comma { - write!(f, ",")?; - } - write!(f, r#""height":{}"#, stage_height)?; - comma = true; - } - } - let mut runtime_options_opened = false; - if let Some(max_clones) = self.max_clones { - if max_clones.is_infinite() { - if comma { - write!(f, ",")?; - } - write!(f, r#""runtimeOptions":{{"#)?; - runtime_options_opened = true; - write!(f, r#""maxClones":Infinity"#,)?; - comma = true; - } else if (max_clones as i64) != 300 { - if comma { - write!(f, ",")?; - } - write!(f, r#""runtimeOptions":{{"#)?; - runtime_options_opened = true; - write!(f, r#""maxClones":{}"#, max_clones as i64)?; - comma = true; - } - } - if let Some(no_miscellaneous_limits) = self.no_miscellaneous_limits { - if no_miscellaneous_limits { - if comma { - write!(f, ",")?; - } - if !runtime_options_opened { - write!(f, r#""runtimeOptions":{{"#)?; - runtime_options_opened = true; - } - write!(f, r#""miscLimits":false"#)?; - comma = true; - } - } - if let Some(no_sprite_fencing) = self.no_sprite_fencing { - if no_sprite_fencing { - if comma { - write!(f, ",")?; - } - if !runtime_options_opened { - write!(f, r#""runtimeOptions":{{"#)?; - runtime_options_opened = true; - } - write!(f, r#""fencing":false"#)?; - } - } - if runtime_options_opened { - write!(f, "}}")?; - } - write!(f, "}} // _twconfig_") - } -} diff --git a/src/diagnostic.rs b/src/diagnostic.rs index 3472677..50077e6 100644 --- a/src/diagnostic.rs +++ b/src/diagnostic.rs @@ -1,18 +1,14 @@ -pub mod keys; +mod diagnostic_kind; +mod project_diagnostics; +mod sprite_diagnostics; -use std::cmp::Ordering; - -use colored::Colorize; -use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; +pub use diagnostic_kind::*; +use lalrpop_util::ParseError; use logos::Span; -use smol_str::SmolStr; +pub use project_diagnostics::*; +pub use sprite_diagnostics::*; -use self::keys::all_keys; -use crate::{ - ast::Sprite, - blocks::{Block, Repr}, - lexer::token::Token, -}; +use crate::lexer::token::Token; #[derive(Debug)] pub struct Diagnostic { @@ -20,291 +16,31 @@ pub struct Diagnostic { pub span: Span, } -#[derive(Debug)] -pub enum DiagnosticKind { - InvalidToken, - UnrecognizedEof(Vec), - UnrecognizedToken(Token, Vec), - ExtraToken(Token), - FileNotFound(SmolStr), - FollowedByUnreachableCode, - UnrecognizedReporter(SmolStr), - UnrecognizedVariable(SmolStr), - UnrecognizedProcedure(SmolStr), - UnrecognizedList(SmolStr), - UnrecognizedKey(SmolStr), - UnrecognizedArgument { name: SmolStr, proc: Option }, - UnrecognizedEnum { enum_name: SmolStr, variant_name: SmolStr }, - UnrecognizedEnumVariant { enum_name: SmolStr, variant_name: SmolStr }, - UnusedVariable(SmolStr), - UnusedProcedure(SmolStr), - UnusedList(SmolStr), - UnusedArgument(SmolStr), - UnusedEnumVariant { enum_name: SmolStr, variant_name: SmolStr }, - BlockArgsCountMismatch { block: Block, given: usize }, - ReprArgsCountMismatch { repr: Repr, given: usize }, - ProcArgsCountMismatch { proc: SmolStr, given: usize }, - NoCostumes, -} - -impl DiagnosticKind { - pub fn to_diagnostic(self, span: Span) -> Diagnostic { - Diagnostic { kind: self, span } - } - - fn message(&self, sprite: &Sprite) -> &'static str { - match self { - Self::InvalidToken => "invalid token", - Self::UnrecognizedEof(_) => "unrecognized end of file", - Self::UnrecognizedToken(_, _) => "unrecognized token", - Self::ExtraToken(_) => "extra token", - Self::FileNotFound(_) => "file not found", - Self::FollowedByUnreachableCode => "this is followed by unreachable code", - Self::UnrecognizedReporter(_) => "unrecognized reporter", - Self::UnrecognizedVariable(_) => "unrecognized variable", - Self::UnrecognizedProcedure(_) => "unrecognized block or procedure", - Self::UnrecognizedList(_) => "unrecognized list", - Self::UnrecognizedKey(_) => "unrecognized key", - Self::UnrecognizedArgument { .. } => "unrecognized argument", - Self::UnrecognizedEnum { .. } => "unrecognized enum", - Self::UnrecognizedEnumVariant { .. } => "unrecognized enum variant", - Self::UnusedVariable(_) => "unused variable", - Self::UnusedProcedure(_) => "unused procedure", - Self::UnusedList(_) => "unused list", - Self::UnusedArgument(_) => "unused argument", - Self::UnusedEnumVariant { .. } => "unused enum variant", - Self::BlockArgsCountMismatch { block, given } => { - match given.cmp(&block.args().len()) { - Ordering::Less => "too few arguments for block", - Ordering::Greater => "too many arguments for block", - Ordering::Equal => unreachable!(), - } - } - Self::ReprArgsCountMismatch { repr, given } => { - match given.cmp(&repr.args().len()) { - Ordering::Less => "too few arguments for reporter", - Ordering::Greater => "too many arguments for reporter", - Ordering::Equal => unreachable!(), - } - } - Self::ProcArgsCountMismatch { proc, given } => { - match given.cmp(&sprite.procs[proc].args.len()) { - Ordering::Less => "too few arguments for procedure", - Ordering::Greater => "too many arguments for procedure", - Ordering::Equal => unreachable!(), - } - } - Self::NoCostumes => "no costumes declared", - } - } - - fn help(&self, sprite: &Sprite) -> Option { - match self { - Self::BlockArgsCountMismatch { block, given: _ } => { - let overloads = Block::overloads(block.name()); - if !overloads.is_empty() { - return Some(format!( - "this block takes:\n - {}", - overloads - .iter() - .map(|block| block.args().join(", ")) - .collect::>() - .join("\n - ") - )); - } - if block.args().is_empty() { - return Some("this block takes no arguments".to_string()); - } - Some(format!("this block takes {}", block.args().join(", "))) - } - Self::ReprArgsCountMismatch { repr, given: _ } => { - let overloads = Repr::overloads(repr.name()); - if !overloads.is_empty() { - return Some(format!( - "this reporter takes:\n - {}", - overloads - .iter() - .map(|repr| repr.args().join(", ")) - .collect::>() - .join("\n - ") - )); - } - if repr.args().is_empty() { - return Some("this reporter takes no arguments".to_string()); - } - Some(format!("this reporter takes {}", repr.args().join(", "))) - } - Self::ProcArgsCountMismatch { proc, given: _ } => Some(format!( - "this procedure takes {}", - sprite.procs[proc] - .args - .iter() - .map(|(name, _)| name) - .cloned() - .collect::>() - .join(", ") - )), - Self::UnrecognizedReporter(name) => { - get_closest_match(name, Repr::all_names().iter().copied()) - } - Self::UnrecognizedProcedure(name) => get_closest_match( - name, - Block::all_names() - .iter() - .copied() - .chain(sprite.procs.keys().map(SmolStr::as_str)), - ), - Self::UnrecognizedArgument { name, proc } => { - let proc = sprite.procs.get(proc.as_ref()?)?; - get_closest_match(name, proc.args.iter().map(|(arg, _)| arg.as_str())) - } - Self::UnrecognizedVariable(name) => { - get_closest_match(name, sprite.vars.keys().map(SmolStr::as_str)) - } - Self::UnrecognizedList(name) => { - get_closest_match(name, sprite.lists.keys().map(SmolStr::as_str)) - } - Self::UnrecognizedKey(name) => get_closest_match(name, all_keys()), - Self::UnrecognizedEnumVariant { enum_name, variant_name } => { - let enum_ = sprite.enums.get(enum_name)?; - get_closest_match( - variant_name, - enum_.variants.iter().map(|(variant, _)| variant.as_str()), - ) - } - _ => None, - } - } - - fn info(&self) -> Option { - None - } - - pub fn log_level(&self) -> LogLevel { - match self { - Self::InvalidToken - | Self::UnrecognizedEof(_) - | Self::UnrecognizedToken(_, _) - | Self::ExtraToken(_) - | Self::FileNotFound(_) - | Self::UnrecognizedReporter(_) - | Self::UnrecognizedVariable(_) - | Self::UnrecognizedProcedure(_) - | Self::UnrecognizedList(_) - | Self::UnrecognizedKey(_) - | Self::UnrecognizedArgument { .. } - | Self::UnrecognizedEnum { .. } - | Self::UnrecognizedEnumVariant { .. } - | Self::BlockArgsCountMismatch { .. } - | Self::ReprArgsCountMismatch { .. } - | Self::ProcArgsCountMismatch { .. } - | Self::NoCostumes => LogLevel::Error, - Self::FollowedByUnreachableCode - | Self::UnusedVariable(_) - | Self::UnusedProcedure(_) - | Self::UnusedList(_) - | Self::UnusedArgument(_) - | Self::UnusedEnumVariant { .. } => LogLevel::Warning, - } - } -} - -pub enum LogLevel { - Error, - Warning, -} - -impl Diagnostic { - pub fn eprint(&self, path: &str, src: &str, sprite: &Sprite, compact: bool) { - let mut line_no = 0; - let mut col_no = 0; - let mut i = 0; - for (line_no1, line) in src.lines().enumerate() { - if i <= self.span.start && self.span.end <= (i + line.len()) { - line_no = line_no1; - col_no = self.span.start - i; - break; - } - i += line.len() + 1; - } - let log_level = self.kind.log_level(); - let header = match log_level { - LogLevel::Error => "error".red(), - LogLevel::Warning => "warning".yellow(), - }; - let help = self.kind.help(sprite); - let line = src.lines().nth(line_no).unwrap(); - if compact { - eprintln!( - "[{}] {}:{}:{}:{} {}", - header, - path, - line_no + 1, - col_no + 1, - col_no + 1 + self.span.len(), - self.kind.message(sprite), - ); - if let Some(help) = help { - eprintln!("{}", help.magenta()); - } - return; - } - eprintln!( - "{}{} {}", - header.bold(), - ":".bold(), - self.kind.message(sprite).bold(), - ); - if self.span == (0..0) { - eprintln!( - " {} {}:{}:{}", - "─→".bold(), - path.blue(), - line_no + 1, - col_no + 1 - ); - return; - } - - eprintln!( - " {} {}:{}:{}", - "╭→".bold(), - path.blue(), - line_no + 1, - col_no + 1 - ); - eprintln!("{}", " │".bold()); - eprintln!("{} {}", format!(" {:4} │", line_no + 1).bold(), line); - let pad = " ".repeat(col_no); - let padn = " ".repeat(self.span.len()); - let line = "─".repeat(self.span.len()).bold(); - eprintln!( - "{} {}{} {}", - " │".bold(), - pad, - match log_level { - LogLevel::Error => line.red(), - LogLevel::Warning => line.yellow(), +impl From> for Diagnostic { + fn from(value: ParseError) -> Self { + match value { + ParseError::InvalidToken { location } => Self { + kind: DiagnosticKind::InvalidToken, + span: location..location + 1, + }, + ParseError::UnrecognizedEof { location, expected } => Self { + kind: DiagnosticKind::UnrecognizedEof(expected), + span: location..location + 1, + }, + ParseError::UnrecognizedToken { + token: (left, token, right), + expected, + } => Self { + kind: DiagnosticKind::UnrecognizedToken(token, expected), + span: left..right, }, - help.unwrap_or_default() - .replace('\n', &format!("\n {pad}{padn}")) - .bold() - .green(), - ); - if let Some(info) = self.kind.info() { - eprintln!("{}", info.magenta()); + ParseError::ExtraToken { + token: (left, token, right), + } => Self { + kind: DiagnosticKind::ExtraToken(token), + span: left..right, + }, + ParseError::User { error } => error, } } } - -fn get_closest_match<'a, T>(pattern: &str, choices: T) -> Option -where T: Iterator { - let matcher = SkimMatcherV2::default(); - let mut matches: Vec<_> = choices - .filter_map(|choice| { - matcher.fuzzy_match(choice, pattern).map(|score| (choice, score)) - }) - .collect(); - matches.sort_by_key(|(_, score)| *score); - matches.last().map(|(choice, _)| format!("did you mean `{choice}`?")) -} diff --git a/src/diagnostic/diagnostic_kind.rs b/src/diagnostic/diagnostic_kind.rs new file mode 100644 index 0000000..508a554 --- /dev/null +++ b/src/diagnostic/diagnostic_kind.rs @@ -0,0 +1,180 @@ +use annotate_snippets::Level; +use smol_str::SmolStr; + +use super::SpriteDiagnostics; +use crate::{ + ast::{Project, Type}, + blocks::{Block, Repr}, + lexer::token::Token, +}; + +#[derive(Debug)] +pub enum DiagnosticKind { + // Errors + InvalidToken, + UnrecognizedEof(Vec), + UnrecognizedToken(Token, Vec), + ExtraToken(Token), + FileNotFound(SmolStr), + UnrecognizedReporter(SmolStr), + UnrecognizedBlock(SmolStr), + UnrecognizedVariable(SmolStr), + UnrecognizedList(SmolStr), + UnrecognizedEnum(SmolStr), + UnrecognizedStruct(SmolStr), + UnrecognizedProcedure(SmolStr), + UnrecognizedArgument(SmolStr), + UnrecognizedStructField(SmolStr), + UnrecognizedEnumVariant(SmolStr), + UnrecognizedKey(SmolStr), + NoCostumes, + BlockArgsCountMismatch { + block: Block, + given: usize, + }, + ReprArgsCountMismatch { + repr: Repr, + given: usize, + }, + ProcArgsCountMismatch { + proc: SmolStr, + given: usize, + }, + CommandFailed { + stderr: Vec, + }, + TypeMismatch { + expected: Type, + given: Type, + }, + NotStruct, + StructDoesNotHaveField { + type_name: SmolStr, + field_name: SmolStr, + }, + // Warnings + FollowedByUnreachableCode, + UnusedVariable(SmolStr), + UnusedList(SmolStr), + UnusedEnum(SmolStr), + UnusedStruct(SmolStr), + UnusedProcedure(SmolStr), + UnusedArgument(SmolStr), + UnusedStructField(SmolStr), + UnusedEnumVariant(SmolStr), +} + +impl DiagnosticKind { + pub fn to_string(&self, project: &Project, sprite_diagnostics: &SpriteDiagnostics) -> String { + match self { + DiagnosticKind::InvalidToken => "invalid token".to_string(), + DiagnosticKind::UnrecognizedEof(vec) => { + format!("unrecognized eof, expected one of {:?}", vec) + } + DiagnosticKind::UnrecognizedToken(token, vec) => { + format!("unrecognized token {:?}, expected one of {:?}", token, vec) + } + DiagnosticKind::ExtraToken(token) => format!("extra token {:?}", token), + DiagnosticKind::FileNotFound(smol_str) => format!("file not found: {:?}", smol_str), + DiagnosticKind::UnrecognizedReporter(name) => format!("unrecognized reporter `{name}`"), + DiagnosticKind::UnrecognizedBlock(name) => format!("unrecognized block `{name}`"), + DiagnosticKind::UnrecognizedVariable(name) => format!("unrecognized variable `{name}`"), + DiagnosticKind::UnrecognizedList(name) => format!("unrecognized list {name}"), + DiagnosticKind::UnrecognizedEnum(name) => format!("unrecognized enum {name}"), + DiagnosticKind::UnrecognizedStruct(name) => format!("unrecognized struct {name}"), + DiagnosticKind::UnrecognizedProcedure(name) => format!("unrecognized procedure {name}"), + DiagnosticKind::UnrecognizedArgument(name) => format!("unrecognized argument {name}"), + DiagnosticKind::UnrecognizedStructField(name) => { + format!("unrecognized struct field {name}") + } + DiagnosticKind::UnrecognizedEnumVariant(name) => { + format!("unrecognized enum variant {name}") + } + DiagnosticKind::UnrecognizedKey(name) => format!("unrecognized key {name}"), + DiagnosticKind::NoCostumes => "no costumes".to_string(), + DiagnosticKind::BlockArgsCountMismatch { block, given } => { + format!( + "block {:?} expects {} arguments, but {} were given", + block, + block.args().len(), + given + ) + } + DiagnosticKind::ReprArgsCountMismatch { repr, given } => { + format!( + "repr {:?} expects {} arguments, but {} were given", + repr, + repr.args().len(), + given + ) + } + DiagnosticKind::ProcArgsCountMismatch { proc, given } => { + format!( + "proc {:?} expects unknown arguments, but {} were given", + proc, given + ) + } + DiagnosticKind::CommandFailed { .. } => "command failed".to_string(), + DiagnosticKind::TypeMismatch { expected, given } => { + format!("type mismatch: expected {}, but got {}", expected, given) + } + DiagnosticKind::FollowedByUnreachableCode => "followed by unreachable code".to_string(), + DiagnosticKind::UnusedVariable(name) => format!("unused variable {name}"), + DiagnosticKind::UnusedList(name) => format!("unused list {name}"), + DiagnosticKind::UnusedEnum(name) => format!("unused enum {name}"), + DiagnosticKind::UnusedStruct(name) => format!("unused struct {name}"), + DiagnosticKind::UnusedProcedure(name) => format!("unused procedure {name}"), + DiagnosticKind::UnusedArgument(name) => format!("unused argument {name}"), + DiagnosticKind::UnusedStructField(name) => format!("unused struct field {name}"), + DiagnosticKind::UnusedEnumVariant(name) => format!("unused enum variant {name}"), + DiagnosticKind::NotStruct => "not a struct".to_string(), + DiagnosticKind::StructDoesNotHaveField { + type_name, + field_name, + } => { + format!("struct {type_name} does not have field {field_name}") + } + } + } +} + +impl From<&DiagnosticKind> for Level { + fn from(val: &DiagnosticKind) -> Self { + match val { + | DiagnosticKind::InvalidToken + | DiagnosticKind::UnrecognizedEof(_) + | DiagnosticKind::UnrecognizedToken(_, _) + | DiagnosticKind::ExtraToken(_) + | DiagnosticKind::FileNotFound(_) + | DiagnosticKind::UnrecognizedReporter(_) + | DiagnosticKind::UnrecognizedBlock(_) + | DiagnosticKind::UnrecognizedVariable(_) + | DiagnosticKind::UnrecognizedList(_) + | DiagnosticKind::UnrecognizedEnum(_) + | DiagnosticKind::UnrecognizedStruct(_) + | DiagnosticKind::UnrecognizedProcedure(_) + | DiagnosticKind::UnrecognizedArgument(_) + | DiagnosticKind::UnrecognizedStructField(_) + | DiagnosticKind::UnrecognizedEnumVariant(_) + | DiagnosticKind::UnrecognizedKey(_) + | DiagnosticKind::NoCostumes + | DiagnosticKind::BlockArgsCountMismatch { .. } + | DiagnosticKind::ReprArgsCountMismatch { .. } + | DiagnosticKind::ProcArgsCountMismatch { .. } + | DiagnosticKind::CommandFailed { .. } + | DiagnosticKind::TypeMismatch { .. } + | DiagnosticKind::NotStruct + | DiagnosticKind::StructDoesNotHaveField { .. } => Level::Error, + + | DiagnosticKind::FollowedByUnreachableCode + | DiagnosticKind::UnusedVariable(_) + | DiagnosticKind::UnusedList(_) + | DiagnosticKind::UnusedEnum(_) + | DiagnosticKind::UnusedStruct(_) + | DiagnosticKind::UnusedProcedure(_) + | DiagnosticKind::UnusedArgument(_) + | DiagnosticKind::UnusedStructField(_) + | DiagnosticKind::UnusedEnumVariant(_) => Level::Warning, + } + } +} diff --git a/src/diagnostic/project_diagnostics.rs b/src/diagnostic/project_diagnostics.rs new file mode 100644 index 0000000..2a935ec --- /dev/null +++ b/src/diagnostic/project_diagnostics.rs @@ -0,0 +1,22 @@ +use annotate_snippets::Renderer; +use fxhash::FxHashMap; +use smol_str::SmolStr; + +use super::SpriteDiagnostics; +use crate::ast::Project; + +pub struct ProjectDiagnostics { + pub project: Project, + pub stage_diagnostics: SpriteDiagnostics, + pub sprites_diagnostics: FxHashMap, +} + +impl ProjectDiagnostics { + pub fn eprint(&self) { + let renderer = Renderer::styled(); + self.stage_diagnostics.eprint(&renderer, &self.project); + for sprite_diagnostics in self.sprites_diagnostics.values() { + sprite_diagnostics.eprint(&renderer, &self.project); + } + } +} diff --git a/src/diagnostic/sprite_diagnostics.rs b/src/diagnostic/sprite_diagnostics.rs new file mode 100644 index 0000000..d619bed --- /dev/null +++ b/src/diagnostic/sprite_diagnostics.rs @@ -0,0 +1,52 @@ +use std::{fs, io, path::PathBuf}; + +use annotate_snippets::{Level, Renderer, Snippet}; +use colored::Colorize; +use logos::Span; + +use super::{diagnostic_kind::DiagnosticKind, Diagnostic}; +use crate::ast::Project; + +pub struct SpriteDiagnostics { + pub path: PathBuf, + pub src: String, + pub diagnostics: Vec, +} + +impl SpriteDiagnostics { + pub fn new(path: PathBuf) -> io::Result { + let src = fs::read_to_string(&path)?; + Ok(Self { + path, + src, + diagnostics: Vec::new(), + }) + } + + pub fn report(&mut self, kind: DiagnosticKind, span: &Span) { + self.diagnostics.push(Diagnostic { + kind, + span: span.clone(), + }); + } + + pub fn eprint(&self, renderer: &Renderer, project: &Project) { + for diagnostic in &self.diagnostics { + let level: Level = (&diagnostic.kind).into(); + let title = diagnostic.kind.to_string(project, self); + let message = level.title(&title).snippet( + Snippet::source(&self.src) + .origin(self.path.to_str().unwrap()) + .fold(true) + .annotation(level.span(diagnostic.span.clone())), + ); + eprintln!("{}", renderer.render(message)); + if let DiagnosticKind::CommandFailed { stderr } = &diagnostic.kind { + eprintln!("{}:", "stderr".red().bold()); + for line in stderr.split(|&b| b == b'\n') { + eprintln!(" {}", std::str::from_utf8(line).unwrap().red()); + } + } + } + } +} diff --git a/src/frontend.rs b/src/frontend.rs index c8d26bc..2415c97 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -1,45 +1,57 @@ -pub mod build; -pub mod new; +mod build; +mod cli; +mod new; -use anyhow::Result; -use clap::{CommandFactory, Parser}; +use std::process::ExitCode; -use crate::{ - cli::{Cli, Commands}, - config::Config, -}; +use clap::{CommandFactory, Parser}; +use cli::{Cli, Command}; +use colored::Colorize; -pub fn frontend() -> Result<()> { +pub fn frontend() -> ExitCode { match Cli::parse().command { - Commands::Build { input, output, compact } => { - build::build(input, output, compact) + Command::Build { input, output } => { + let result = build::build(input, output); + match result { + Ok(()) => ExitCode::SUCCESS, + Err(build::BuildError::AnyhowError(err)) => { + eprintln!("{}: {:?}", "error".red().bold(), err); + ExitCode::FAILURE + } + Err(build::BuildError::ProjectDiagnostics(diagnostics)) => { + diagnostics.eprint(); + eprintln!(); + ExitCode::FAILURE + } + } } - Commands::New { - name, - frame_rate, - max_clones, - no_miscellaneous_limits, - no_sprite_fencing, - frame_interpolation, - high_quality_pen, - stage_width, - stage_height, - } => new::new( - name, - Config { - frame_rate, - max_clones, - no_miscellaneous_limits: no_miscellaneous_limits.then_some(true), - no_sprite_fencing: no_sprite_fencing.then_some(true), - frame_interpolation: frame_interpolation.then_some(true), - high_quality_pen: high_quality_pen.then_some(true), - stage_width, - stage_height, - }, - ), - Commands::Completions { shell } => { + Command::Completions { shell } => { shell.generate(&mut Cli::command(), &mut std::io::stdout()); - Ok(()) + ExitCode::SUCCESS } + _ => panic!(), + // Command::New { + // name, + // frame_rate, + // max_clones, + // no_miscellaneous_limits, + // no_sprite_fencing, + // frame_interpolation, + // high_quality_pen, + // stage_width, + // stage_height, + // } => new::new( + // name, + // Config { + // frame_rate, + // max_clones, + // no_miscellaneous_limits: Some(no_miscellaneous_limits), + // no_sprite_fencing: Some(no_sprite_fencing), + // frame_interpolation: Some(frame_interpolation), + // high_quality_pen: Some(high_quality_pen), + // stage_width, + // stage_height, + // }, + // ), } } diff --git a/src/frontend/build.rs b/src/frontend/build.rs index ff86484..a59635d 100644 --- a/src/frontend/build.rs +++ b/src/frontend/build.rs @@ -1,148 +1,131 @@ use std::{ env, - fs::{self, read_dir, File}, - io::{self, BufWriter}, + fs::{self, File}, + io::BufWriter, path::PathBuf, }; -use anyhow::{bail, Result}; -use colored::Colorize; +use anyhow::{anyhow, Context}; use fxhash::FxHashMap; +use log::info; use smol_str::SmolStr; use crate::{ ast::{Project, Sprite}, - codegen::Sb3, + codegen::sb3::Sb3, config::Config, - custom_toml_error::CustomTOMLError, - diagnostic::{Diagnostic, LogLevel}, - parser::parse, - visitors::{pass1, pass2}, + diagnostic::{ProjectDiagnostics, SpriteDiagnostics}, + parser, visitor, }; -pub fn build( - input: Option, - output: Option, - compact: bool, -) -> Result<()> { - let input = if let Some(input) = input { input } else { env::current_dir()? }; +pub enum BuildError { + AnyhowError(anyhow::Error), + ProjectDiagnostics(ProjectDiagnostics), +} + +impl From for BuildError +where T: Into +{ + fn from(value: T) -> Self { + Self::AnyhowError(value.into()) + } +} + +impl From for BuildError { + fn from(value: ProjectDiagnostics) -> Self { + Self::ProjectDiagnostics(value) + } +} + +pub fn build(input: Option, output: Option) -> Result<(), BuildError> { + let input = input.unwrap_or_else(|| env::current_dir().unwrap()); let canonical_input = input.canonicalize()?; let project_name = canonical_input.file_name().unwrap().to_str().unwrap(); let output = output.unwrap_or_else(|| input.join(format!("{project_name}.sb3"))); let config_path = input.join("goboscript.toml"); - let config = if let Ok(config_src) = fs::read_to_string(&config_path) { - match toml::from_str::(&config_src) { - Ok(config) => config, - Err(err) => { - eprintln!("{}", CustomTOMLError::new(config_path, config_src, err)); - bail!("cannot continue due to syntax errors") - } - } - } else { - Default::default() - }; + let config_src = fs::read_to_string(&config_path).unwrap_or_default(); + let config: Config = toml::from_str(&config_src) + .with_context(|| format!("failed to parse {}", config_path.display()))?; let stage_path = input.join("stage.gs"); - let stage_src = match fs::read_to_string(&stage_path) { - Ok(src) => src, - Err(err) => { - if matches!(err.kind(), io::ErrorKind::NotFound) { - bail!("`stage.gs` not found, is this a goboscript project?") - } - bail!(err) - } - }; - let stage = match parse(&stage_src) { - Ok(stage) => stage, - Err(diag) => { - diag.eprint( - stage_path.to_str().unwrap(), - &stage_src, - &Default::default(), - compact, - ); - bail!("cannot continue due to syntax errors") - } - }; + if !stage_path.is_file() { + return Err(anyhow!("{} not found", stage_path.display()).into()); + } + let mut stage_diagnostics = SpriteDiagnostics::new(stage_path)?; + let stage = parser::parse(&stage_diagnostics.src) + .map_err(|err| { + stage_diagnostics.diagnostics.push(err); + }) + .unwrap_or_default(); + let mut sprites_diagnostics: FxHashMap = Default::default(); let mut sprites: FxHashMap = Default::default(); - let mut stage_diags: Vec = Default::default(); - let mut srcs: FxHashMap = Default::default(); - let mut diags: FxHashMap> = Default::default(); - for path in read_dir(&input)?.flatten().map(|entry| entry.path()) { - if !(path.extension() == Some("gs".as_ref()) - && path.file_stem() != Some("stage".as_ref()) - && path.is_file()) + for sprite_path in fs::read_dir(&input)? { + let sprite_path = sprite_path?.path(); + if sprite_path.file_stem().is_some_and(|stem| stem == "stage") { + continue; + } + if !sprite_path + .extension() + .is_some_and(|extension| extension == "gs") { continue; } - let src = fs::read_to_string(&path)?; - let sprite = match parse(&src) { - Ok(sprite) => sprite, - Err(diag) => { - diag.eprint(path.to_str().unwrap(), &src, &Default::default(), compact); - bail!("cannot continue due to syntax errors") - } - }; - let name = SmolStr::from(path.file_stem().unwrap().to_str().unwrap()); - sprites.insert(name.clone(), sprite); - srcs.insert(name.clone(), (path, src)); - diags.insert(name.clone(), Default::default()); - } - let mut project = Project::new(stage, sprites); - pass1::visit_project(&mut project); - pass2::visit_project(&mut project); - let mut sb3 = Sb3::new(BufWriter::new(File::create(output)?)); - sb3.package(&project, &config, &input, &mut stage_diags, &mut diags)?; - let mut errors = 0; - let mut warnings = 0; - for diag in stage_diags { - diag.eprint(stage_path.to_str().unwrap(), &stage_src, &project.stage, compact); - match diag.kind.log_level() { - LogLevel::Error => errors += 1, - LogLevel::Warning => warnings += 1, - }; - } - for (name, diags) in diags { - for diag in diags { - let (path, src) = &srcs[&name]; - diag.eprint(path.to_str().unwrap(), src, &project.sprites[&name], compact); - match diag.kind.log_level() { - LogLevel::Error => errors += 1, - LogLevel::Warning => warnings += 1, - }; + if !sprite_path.is_file() { + continue; } + let sprite_name: SmolStr = sprite_path + .file_stem() + .unwrap_or_default() + .to_str() + .unwrap() + .into(); + let mut sprite_diagnostics = SpriteDiagnostics::new(sprite_path)?; + let sprite = parser::parse(&sprite_diagnostics.src) + .map_err(|err| sprite_diagnostics.diagnostics.push(err)) + .unwrap_or_default(); + sprites_diagnostics.insert(sprite_name.clone(), sprite_diagnostics); + sprites.insert(sprite_name, sprite); } - let err_summary = make_err_summary(errors, warnings); - if errors > 0 { - bail!(err_summary); + let mut project = Project { stage, sprites }; + if !(stage_diagnostics.diagnostics.is_empty() + && sprites_diagnostics + .values() + .all(|sprite_diagnostics| sprite_diagnostics.diagnostics.is_empty())) + { + return Err(ProjectDiagnostics { + project, + stage_diagnostics, + sprites_diagnostics, + } + .into()); } - if warnings > 0 { - eprintln!("{}: {}", "warning".yellow().bold(), err_summary.bold()) + info!(target: "parse", "{project:#?}"); + visitor::pass0::visit_project(&mut project); + info!(target: "pass0", "{project:#?}"); + visitor::pass1::visit_project( + &mut project, + &mut stage_diagnostics, + &mut sprites_diagnostics, + ); + info!(target: "pass1", "{project:#?}"); + let mut sb3 = Sb3::new(BufWriter::new(File::create(output)?)); + sb3.project( + &input, + &project, + &config, + &mut stage_diagnostics, + &mut sprites_diagnostics, + )?; + if !(stage_diagnostics.diagnostics.is_empty() + && sprites_diagnostics + .values() + .all(|sprite_diagnostics| sprite_diagnostics.diagnostics.is_empty())) + { + return Err(ProjectDiagnostics { + project, + stage_diagnostics, + sprites_diagnostics, + } + .into()); } Ok(()) } - -fn make_err_summary(errors: i32, warnings: i32) -> String { - let error_text = if errors == 1 { - Some(String::from("one error generated")) - } else if errors > 1 { - Some(format!("{errors} errors generated")) - } else { - None - }; - let warning_text = if warnings == 1 { - Some(String::from("one warning generated")) - } else if warnings > 1 { - Some(format!("{warnings} warnings generated")) - } else { - None - }; - if let (Some(errs), Some(warns)) = (&error_text, &warning_text) { - format!("{errs}; {warns}") - } else if let Some(errs) = error_text { - errs - } else if let Some(warns) = warning_text { - warns - } else { - "".into() - } -} diff --git a/src/frontend/cli.rs b/src/frontend/cli.rs new file mode 100644 index 0000000..db06ea5 --- /dev/null +++ b/src/frontend/cli.rs @@ -0,0 +1,74 @@ +use std::path::PathBuf; + +use clap_derive::{Parser, Subcommand}; + +#[derive(Debug, Parser)] +#[command( + version = env!("CARGO_PKG_VERSION"), +)] +pub struct Cli { + #[command(subcommand)] + pub command: Command, +} + +#[derive(Debug, Subcommand)] +pub enum Command { + /// Compile a goboscript project to `.sb3` + #[command()] + Build { + #[arg(short, long)] + /// Project directory, if not given, the current directory is used. + input: Option, + #[arg(short, long)] + /// Output file, if not given, it will be the project directory's name + `.sb3` + output: Option, + }, + + /// Create a new goboscript project with a blank backdrop, a main sprite with a + /// blank costume. + #[command()] + New { + /// Name of directory to create new project, if not given, the current directory + /// is used. If this is a path to an existing directory, it must be empty. + #[arg(short = 'n', long)] + name: Option, + + /// (alias: --fps) Custom frame rate, used by TurboWarp. + #[arg(short = 'f', long, alias = "fps")] + frame_rate: Option, + + /// (alias: --clones) Custom maximum number of clones allowed, used by TurboWarp. + /// Use `--max-clones inf` for infinite clones. + #[arg(short = 'c', long, alias = "clones")] + max_clones: Option, + + /// (alias: --limitless) Disable miscellaneous limits, used by TurboWarp. + #[arg(short = 'l', long, alias = "limitless")] + no_miscellaneous_limits: bool, + + /// (alias: --offscreen) Disable sprite fencing, used by TurboWarp. + #[arg(short = 'o', long, alias = "offscreen")] + no_sprite_fencing: bool, + + /// (alias: --interpolate) Enable frame interpolation, used by TurboWarp. + #[arg(short = 'i', long, alias = "interpolate")] + frame_interpolation: bool, + + /// (alias: --hqpen) Enable high quality pen, used by TurboWarp. + #[arg(short = 'q', long, alias = "hqpen")] + high_quality_pen: bool, + + /// (alias: --width) Custom stage width, used by TurboWarp. + #[arg(short = 'W', long, alias = "width")] + stage_width: Option, + + /// (alias: --height) Custom stage height, used by TurboWarp. + #[arg(short = 'H', long, alias = "height")] + stage_height: Option, + }, + Completions { + /// The shell to generate the completions for. + #[arg(value_enum)] + shell: clap_complete_command::Shell, + }, +} diff --git a/src/frontend/new.rs b/src/frontend/new.rs index 9d075b2..19f2827 100644 --- a/src/frontend/new.rs +++ b/src/frontend/new.rs @@ -1,38 +1,28 @@ -use std::{ - env, - fs::{self, create_dir}, - path::PathBuf, -}; +// use std::{env, fs, path::PathBuf}; -use anyhow::{bail, Result}; +// use anyhow::bail; -use crate::config::Config; +// use crate::config::Config; -macro_rules! write_templates { - ($input:expr, $($file:expr),*) => { - $( - fs::write($input.join($file), include_str!(concat!("templates/", $file)))?; - )* - }; -} +// macro_rules! write_templates { +// ($input:expr, $($file:expr),*) => { +// $( +// fs::write($input.join($file), include_str!(concat!("templates/", $file)))?; +// )* +// }; +// } -pub fn new(input: Option, config: Config) -> Result<()> { - let input_explicit = input.is_some(); - let input = if let Some(input) = input { input } else { env::current_dir()? }; - if let Err(err) = create_dir(&input) { - if !matches!(err.kind(), std::io::ErrorKind::AlreadyExists) { - bail!(err); - } - } - if input.read_dir()?.count() > 0 { - if input_explicit { - bail!("directory is not empty"); - } - bail!("current directory is not empty, provide a `--name` argument"); - } - if config != Default::default() { - fs::write(input.join("goboscript.toml"), toml::to_string(&config)?)?; - } - write_templates!(input, "stage.gs", "main.gs", "blank.svg"); - Ok(()) -} +// pub fn new(name: Option, config: Config) -> anyhow::Result<()> { +// let is_name_explicit = name.is_some(); +// let name = name.unwrap_or_else(|| env::current_dir().unwrap()); +// fs::create_dir(&name)?; +// if name.read_dir()?.count() > 0 { +// return Err(Error::NewDirNotEmpty { name, is_name_explicit }); +// } +// let config_path = name.join("goboscript.toml"); +// if config != Default::default() { +// fs::write(config_path, toml::to_string(&config).unwrap())?; +// } +// write_templates!(name, "stage.gs", "main.gs", "blank.svg"); +// Ok(()) +// } diff --git a/src/lexer/adaptor.rs b/src/lexer/adaptor.rs index 4935f08..851d94a 100644 --- a/src/lexer/adaptor.rs +++ b/src/lexer/adaptor.rs @@ -9,7 +9,9 @@ pub struct Lexer<'source> { impl<'source> Lexer<'source> { pub fn new(source: &'source str) -> Self { - Self { token_stream: Token::lexer(source).spanned() } + Self { + token_stream: Token::lexer(source).spanned(), + } } } @@ -26,7 +28,10 @@ impl<'source> Iterator for Lexer<'source> { self.token_stream.next().map(|(token, span)| { token .map(|token| (span.start, token, span.end)) - .map_err(|_| DiagnosticKind::InvalidToken.to_diagnostic(span)) + .map_err(|_| Diagnostic { + kind: DiagnosticKind::InvalidToken, + span: span.clone(), + }) }) } } diff --git a/src/lexer/literal.rs b/src/lexer/literal.rs index 4c93c0f..20cd402 100644 --- a/src/lexer/literal.rs +++ b/src/lexer/literal.rs @@ -11,6 +11,10 @@ pub fn string(lex: &mut Lexer) -> SmolStr { SmolStr::from(serde_json::from_str::<'_, String>(lex.slice()).unwrap()) } +pub fn cmd(lex: &mut Lexer) -> SmolStr { + SmolStr::from(&lex.slice()[3..lex.slice().len() - 3]) +} + pub fn arg(lex: &mut Lexer) -> SmolStr { SmolStr::from(&lex.slice()[1..]) } diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 0d7a3e5..dfb47bd 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -1,7 +1,7 @@ use logos::Logos; use smol_str::SmolStr; -use super::literal::{arg, bin, float, hex, int, mac, name, oct, string}; +use super::literal::{arg, bin, cmd, float, hex, int, mac, name, oct, string}; #[derive(Debug, Logos, Clone)] #[logos(skip r"[ \t\n\f]+")] @@ -25,6 +25,8 @@ pub enum Token { Float(f64), #[regex(r#""([^"\\]|\\["\\/bfnrt]|\\u[0-9a-zA-Z]{4})*""#, string)] Str(SmolStr), + #[regex(r#"```([^`]|\n)*```"#, cmd)] + Cmd(SmolStr), #[token("costumes")] Costumes, #[token("sounds")] @@ -187,8 +189,12 @@ pub enum Token { As, #[token("enum")] Enum, + #[token("struct")] + Struct, #[token("true")] True, #[token("false")] False, + #[token("list")] + List, } diff --git a/src/main.rs b/src/main.rs index f3ba25a..5bdcbe6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,37 +1,28 @@ -use std::{panic, process::ExitCode, time::Instant}; - -use colored::Colorize; - mod ast; mod blocks; -mod cli; mod codegen; mod config; -mod custom_toml_error; mod diagnostic; mod frontend; mod lexer; +mod misc; mod parser; -mod preproc; -mod visitors; +mod visitor; +use std::{process::ExitCode, time::Instant}; + +use colored::Colorize; fn main() -> ExitCode { - panic::set_hook(Box::new(|info| { + pretty_env_logger::init(); + std::panic::set_hook(Box::new(|info| { eprintln!( - "{info}\n\n{} 💀\nor open an issue at {}", - "Let's pretend that didn't happen".bold(), - "https://github.com/aspizu/goboscript/issues".blue() + "{info}\n{}\nopen an issue at {}", + "-9999 aura 💀".red().bold(), + "https://github.com/aspizu/goboscript/issues".cyan() ); })); let begin = Instant::now(); let result = frontend::frontend(); - if let Err(err) = &result { - eprintln!("{}{} {}", "error".bold().red(), ":".bold(), err.to_string().bold()); - } - eprintln!("{} in {:?}", "finished".bold().blue(), begin.elapsed()); - if result.is_ok() { - ExitCode::SUCCESS - } else { - ExitCode::FAILURE - } + eprintln!("{} in {:?}", "Finished".green().bold(), begin.elapsed()); + result } diff --git a/src/misc.rs b/src/misc.rs new file mode 100644 index 0000000..1ae9e80 --- /dev/null +++ b/src/misc.rs @@ -0,0 +1,22 @@ +use core::fmt; +use std::{cell::RefCell, io, rc::Rc}; + +pub fn write_comma_io(mut file: T, comma: &mut bool) -> io::Result<()> +where T: io::Write { + if *comma { + file.write_all(b",")?; + } + *comma = true; + Ok(()) +} + +pub fn write_comma_fmt(mut file: T, comma: &mut bool) -> fmt::Result +where T: fmt::Write { + if *comma { + file.write_str(",")?; + } + *comma = true; + Ok(()) +} + +pub type Rrc = Rc>; diff --git a/src/parser.rs b/src/parser.rs index e7941a7..f0e9a66 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,43 +1,14 @@ -use lalrpop_util::{lalrpop_mod, ParseError}; +use grammar::SpriteParser; +use lalrpop_util::lalrpop_mod; -use crate::{lexer::adaptor::Lexer, parser::grammar::SpriteParser}; +use crate::{ast::Sprite, diagnostic::Diagnostic, lexer::adaptor::Lexer}; lalrpop_mod!(grammar, "/parser/grammar.rs"); -use crate::{ - ast::Sprite, - diagnostic::{Diagnostic, DiagnosticKind}, - preproc, -}; - pub fn parse(src: &str) -> Result { - let tokens = preproc::process( - Lexer::new(src).flatten().map(|(left, token, right)| (token, left..right)), - &mut Default::default(), - ); + let tokens = Lexer::new(src).flatten(); let parser = SpriteParser::new(); let mut sprite = Sprite::default(); - parser - .parse( - &mut sprite, - tokens.into_iter().map(|(token, span)| (span.start, token, span.end)), - ) - .map(|_| sprite) - .map_err(|err| match err { - ParseError::InvalidToken { location } => { - DiagnosticKind::InvalidToken.to_diagnostic(location..location + 1) - } - ParseError::UnrecognizedEof { location, expected } => { - DiagnosticKind::UnrecognizedEof(expected) - .to_diagnostic(location..location + 1) - } - ParseError::UnrecognizedToken { token: (left, token, right), expected } => { - DiagnosticKind::UnrecognizedToken(token, expected) - .to_diagnostic(left..right) - } - ParseError::ExtraToken { token: (left, token, right) } => { - DiagnosticKind::ExtraToken(token).to_diagnostic(left..right) - } - ParseError::User { error } => error, - }) + parser.parse(&mut sprite, tokens)?; + Ok(sprite) } diff --git a/src/parser/grammar.lalrpop b/src/parser/grammar.lalrpop index 2aace3f..a43dbf2 100644 --- a/src/parser/grammar.lalrpop +++ b/src/parser/grammar.lalrpop @@ -1,198 +1,266 @@ -use lalrpop_util::ParseError; -use logos::Span; use smol_str::SmolStr; - -use crate::{ - ast::*, - blocks::*, - diagnostic::{Diagnostic, DiagnosticKind}, - lexer::token::Token, -}; +use logos::Span; +use lalrpop_util::ParseError; +use crate::ast::*; +use crate::blocks::*; +use crate::diagnostic::*; +use crate::lexer::token::*; +use crate::lexer::adaptor::*; +use crate::misc::*; grammar<'a>(sprite: &'a mut Sprite); pub Sprite = Declr*; -Costume: () = { - )?> => { - sprite.costumes.push(Costume::new(name, l..r, alias)); - } +#[inline] +Type: Type = { + => Type::Struct { name: n, span: l..r }, + => Type::Value, } +Arg: Arg = { + => Arg { name: n, span: l..r, type_: t }, +} + +Stmts: Vec = "{" "}"; + Declr: () = { COSTUMES Comma ";" => {}, - PROC > => { - sprite.procs.insert(n.clone(), Proc::new(n.clone(), l..r, a, b, w.is_none())); + PROC > => { + sprite.procs.insert(n.clone(), Proc::new(n, l..r, a, b, w.is_none())); }, - ONFLAG => { - sprite.events.push(EventDetail::OnFlag.to_event(l..r, b)); + ONFLAG => sprite.events.push(EventKind::OnFlag.to_event(l..r, b)), + ONKEY => sprite.events.push(EventKind::OnKey { key, span: kl..kr }.to_event(l..r, b)), + ONCLICK => sprite.events.push(EventKind::OnClick.to_event(l..r, b)), + ONBACKDROP => sprite.events.push(EventKind::OnBackdrop { backdrop, span: kl..kr }.to_event(l..r, b)), + ONLOUDNESS ">" => sprite.events.push(EventKind::OnLoudnessGt { value }.to_event(l..r, b)), + ONTIMER ">" => sprite.events.push(EventKind::OnTimerGt { value }.to_event(l..r, b)), + ONCLONE => sprite.events.push(EventKind::OnClone.to_event(l..r, b)), + STRUCT "{" > "}" => { + sprite.structs.insert(name.clone(), Struct::new(name, l..r, fields)); }, - ONKEY => { - sprite.events.push(EventDetail::OnKey { key, span: kl..kr }.to_event(l..r, b)); + LIST ";" => { + sprite.lists.insert(name.clone(), List { name, span: l..r, type_: t, cmd: None }); }, - ONCLICK => { - sprite.events.push(EventDetail::OnClick.to_event(l..r, b)); + LIST "=" ";" => { + sprite.lists.insert(name.clone(), List { name, span: l..r, type_: t, cmd: Some(Cmd { cmd, span: cl..cr }) }); }, - ONBACKDROP => { - sprite.events.push(EventDetail::OnBackdrop { backdrop, span: kl..kr }.to_event(l..r, b)); +} + +Stmt: Stmt = { + , + REPEAT => Stmt::Repeat { times: t, body: b }, + FOREVER => Stmt::Forever { body: b, span: l..r }, + UNTIL => Stmt::Until { cond: c, body: b }, + "+=" ";" => Stmt::SetVar { name: Name::Name { name: n.clone(), span: l..r }, value: BinOp::Add.to_expr(l..r, Expr::Name(Name::Name { name: n, span: l..r }).into(), v).into(), type_: Type::Value, is_local: false }, + "-=" ";" => Stmt::SetVar { name: Name::Name { name: n.clone(), span: l..r }, value: BinOp::Sub.to_expr(l..r, Expr::Name(Name::Name { name: n, span: l..r }).into(), v).into(), type_: Type::Value, is_local: false }, + "*=" ";" => Stmt::SetVar { name: Name::Name { name: n.clone(), span: l..r }, value: BinOp::Mul.to_expr(l..r, Expr::Name(Name::Name { name: n, span: l..r }).into(), v).into(), type_: Type::Value, is_local: false }, + "/=" ";" => Stmt::SetVar { name: Name::Name { name: n.clone(), span: l..r }, value: BinOp::Div.to_expr(l..r, Expr::Name(Name::Name { name: n, span: l..r }).into(), v).into(), type_: Type::Value, is_local: false }, + "//=" ";" => Stmt::SetVar { name: Name::Name { name: n.clone(), span: l..r }, value: BinOp::FloorDiv.to_expr(l..r, Expr::Name(Name::Name { name: n, span: l..r }).into(), v).into(), type_: Type::Value, is_local: false }, + "%=" ";" => Stmt::SetVar { name: Name::Name { name: n.clone(), span: l..r }, value: BinOp::Mod.to_expr(l..r, Expr::Name(Name::Name { name: n, span: l..r }).into(), v).into(), type_: Type::Value, is_local: false }, + "&=" ";" => Stmt::SetVar { name: Name::Name { name: n.clone(), span: l..r }, value: BinOp::Join.to_expr(l..r, Expr::Name(Name::Name { name: n, span: l..r }).into(), v).into(), type_: Type::Value, is_local: false }, + "=" ";" => Stmt::SetVar { name: Name::Name { name: n, span: l..r }, value: v, type_: t, is_local: false }, + LOCAL "=" ";" => Stmt::SetVar { name: Name::Name { name: n, span: l..r }, value: v, type_: t, is_local: true }, + "." "=" ";" => Stmt::SetVar { name: Name::DotName { lhs: n, lhs_span: l..r, rhs: f, rhs_span: lf..rf }, value: v, type_: Type::Value, is_local: false }, + "." "+=" ";" => Stmt::SetVar { name: Name::DotName { lhs: n.clone(), lhs_span: l..r, rhs: f.clone(), rhs_span: lf..rf }, value: BinOp::Add.to_expr(l..r, Expr::Name(Name::DotName { lhs: n, lhs_span: l..r, rhs: f, rhs_span: lf..rf }).into(), v).into(), type_: Type::Value, is_local: false }, + SHOW ";" => Stmt::Show (name), + HIDE ";" => Stmt::Hide (name), + SHOW ";" => Stmt::Block { block: Block::Show, span: l..r, args: vec![] }, + HIDE ";" => Stmt::Block { block: Block::Hide, span: l..r, args: vec![] }, + ADD TO ";" => Stmt::AddToList { name: n, value: v }, + DELETE ";" => Stmt::DeleteList (name), + DELETE "[" "]" ";" => Stmt::DeleteListIndex { name, index }, + "[" "]" "=" ";" => Stmt::SetListIndex { name, index, value }, + "[" "]" "+=" ";" => Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Add.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() }, + "[" "]" "-=" ";" => Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Sub.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() }, + "[" "]" "*=" ";" => Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Mul.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() }, + "[" "]" "/=" ";" => Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Div.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() }, + "[" "]" "//=" ";" => Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::FloorDiv.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() }, + "[" "]" "%=" ";" => Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Mod.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() }, + "[" "]" "&=" ";" => Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Join.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() }, + "[" "]" "." "=" ";" => Stmt::SetListIndex { name: Name::DotName { lhs: name, lhs_span: l..r, rhs: f, rhs_span: lf..rf }, index, value }, + "[" "]" "." "+=" ";" => { + let name = Name::DotName { lhs: name, lhs_span: l..r, rhs: f, rhs_span: lf..rf }; + Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Add.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() } }, - ONLOUDNESS ">" => { - sprite.events.push(EventDetail::OnLoudnessGt { value }.to_event(l..r, b)); + "[" "]" "." "-=" ";" => { + let name = Name::DotName { lhs: name, lhs_span: l..r, rhs: f, rhs_span: lf..rf }; + Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Sub.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() } }, - ONTIMER ">" => { - sprite.events.push(EventDetail::OnTimerGt { value }.to_event(l..r, b)); + "[" "]" "." "*=" ";" => { + let name = Name::DotName { lhs: name, lhs_span: l..r, rhs: f, rhs_span: lf..rf }; + Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Mul.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() } }, - ON => { - sprite.on_messages.insert(k.clone(), OnMessage::new(k, l..r, b)); + "[" "]" "." "/=" ";" => { + let name = Name::DotName { lhs: name, lhs_span: l..r, rhs: f, rhs_span: lf..rf }; + Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Div.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() } }, - ONCLONE => { - sprite.events.push(EventDetail::OnClone.to_event(l..r, b)); + "[" "]" "." "//=" ";" => { + let name = Name::DotName { lhs: name, lhs_span: l..r, rhs: f, rhs_span: lf..rf }; + Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::FloorDiv.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() } }, - ENUM "{" > "}" => { - sprite.enums.insert(name.clone(), Enum::new(name, l..r, variants)); - } -} - -Stmts: Stmts = "{" "}"; - -Stmt: Stmt = { - , - REPEAT => Stmt::Repeat { times, body }, - FOREVER => Stmt::Forever { body, span: l..r }, - UNTIL => Stmt::Until { cond, body }, - LOCAL "=" ";" => { - Stmt::SetVar { name: name.clone(), span: l..r, value, is_local: true } + "[" "]" "." "%=" ";" => { + let name = Name::DotName { lhs: name, lhs_span: l..r, rhs: f, rhs_span: lf..rf }; + Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Mod.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() } }, - "=" ";" => { - if !sprite.vars.contains_key(&name) { - sprite.vars.insert(name.clone(), Var::new(name.clone(), l..r, None)); - } - Stmt::SetVar { name: name.clone(), span: l..r, value, is_local: false } + "[" "]" "." "&=" ";" => { + let name = Name::DotName { lhs: name, lhs_span: l..r, rhs: f, rhs_span: lf..rf }; + Stmt::SetListIndex { name: name.clone(), index: index.clone(), value: BinOp::Join.to_expr(name.span().clone(), BinOp::Of.to_expr(name.span().clone(), Expr::Name(name).into(), index).into(), value).into() } }, - "++" ";" => Stmt::ChangeVar { name, span: l..r, value: Expr::Int(1).into() }, - "--" ";" => Stmt::ChangeVar { name, span: l..r, value: Expr::Int(-1).into() }, - "+=" ";" => Stmt::ChangeVar { name, span: l..r, value }, - "-=" ";" => Stmt::ChangeVar { name, span: l..r, value: BinOp::Sub .to_expr(Expr::Int(0).into(), value).into()}, - "*=" ";" => Stmt::SetVar { name: name.clone(), span: l..r, value: BinOp::Mul .to_expr(Expr::Name { name: name.clone(), span: l..r }.into(), value).into(), is_local: false }, - "/=" ";" => Stmt::SetVar { name: name.clone(), span: l..r, value: BinOp::Div .to_expr(Expr::Name { name: name.clone(), span: l..r }.into(), value).into(), is_local: false }, - "//=" ";" => Stmt::SetVar { name: name.clone(), span: l..r, value: BinOp::FloorDiv .to_expr(Expr::Name { name: name.clone(), span: l..r }.into(), value).into(), is_local: false }, - "%=" ";" => Stmt::SetVar { name: name.clone(), span: l..r, value: BinOp::Mod .to_expr(Expr::Name { name: name.clone(), span: l..r }.into(), value).into(), is_local: false }, - "&=" ";" => Stmt::SetVar { name: name.clone(), span: l..r, value: BinOp::Join.to_expr(Expr::Name { name: name.clone(), span: l..r }.into(), value).into(), is_local: false }, - SHOW ";" => Stmt::Show { name, span: l..r }, - HIDE ";" => Stmt::Hide { name, span: l..r }, - SHOW ";" => Stmt::Block { block: Block::Show, span: l..r, args: vec![] }, - HIDE ";" => Stmt::Block { block: Block::Hide, span: l..r, args: vec![] }, - ADD TO ";" => Stmt::ListAdd { name, span: l..r, value }, - DELETE ";" => { - if !sprite.lists.contains_key(&name) { - sprite.lists.insert(name.clone(), List::new(name.clone(), l..r, Default::default())); - } - Stmt::ListDeleteAll { name, span: l..r } + INSERT AT "[" "]" ";" => Stmt::InsertAtList { name, index, value }, + > ";" => match Block::from_shape(&name, a.len()) { + Some(block) => Stmt::Block { block, span: l..r, args: a }, + None => Stmt::ProcCall { name, span: l..r, args: a }, }, - DELETE "[" "]" ";" => Stmt::ListDelete { name, span: l..r, index }, - INSERT AT "[" "]" ";" => Stmt::ListInsert { name, span: l..r, index, value }, - "[" "]" "=" ";" => Stmt::ListSet { name, span: l..r, index, value }, - "[" "]" "+=" ";" => Stmt::ListChange { op: BinOp::Add, name, span: l..r, index, value }, - "[" "]" "++" ";" => Stmt::ListChange { op: BinOp::Add, name, span: l..r, index, value: Expr::Int(1).into() }, - "[" "]" "--" ";" => Stmt::ListChange { op: BinOp::Add, name, span: l..r, index, value: Expr::Int(-1).into() }, - "[" "]" "-=" ";" => Stmt::ListChange { op: BinOp::Sub, name, span: l..r, index, value }, - "[" "]" "*=" ";" => Stmt::ListChange { op: BinOp::Mul, name, span: l..r, index, value }, - "[" "]" "/=" ";" => Stmt::ListChange { op: BinOp::Div, name, span: l..r, index, value }, - "[" "]" "//=" ";" => Stmt::ListChange { op: BinOp::FloorDiv, name, span: l..r, index, value }, - "[" "]" "%=" ";" => Stmt::ListChange { op: BinOp::Mod, name, span: l..r, index, value }, - "[" "]" "&=" ";" => Stmt::ListChange { op: BinOp::Join, name, span: l..r, index, value }, - ";" => match Block::from_shape(&name, args.len()) { - Some(block) => Stmt::Block { block, span: l..r, args }, - None => Stmt::ProcCall { name, span: l..r, args }, - } } If: Stmt = { - IF => Stmt::Branch { cond, if_body, else_body: Default::default() }, - IF ELSE => Stmt::Branch { cond, if_body, else_body }, - IF => Stmt::Branch { cond, if_body, else_body: vec![else_body] }, + IF => Stmt::Branch { cond, if_body, else_body: Default::default() }, + IF ELSE => Stmt::Branch { cond, if_body, else_body }, + IF => Stmt::Branch { cond, if_body, else_body: vec![else_body] }, } Elif: Stmt = { - ELIF => Stmt::Branch { cond, if_body: body, else_body: Default::default() }, - ELIF => Stmt::Branch { cond, if_body: body, else_body: vec![else_body] }, - ELIF ELSE => Stmt::Branch { cond, if_body: body, else_body }, + ELIF => Stmt::Branch { cond, if_body: body, else_body: Default::default() }, + ELIF => Stmt::Branch { cond, if_body: body, else_body: vec![else_body] }, + ELIF ELSE => Stmt::Branch { cond, if_body: body, else_body }, +} + +Costume: () = { + )?> => { + sprite.costumes.push(Costume::new(path, alias, l..r)); + } } +Exprs: Vec> = >; + Expr: Rrc = { + #[precedence(level="1")] + StructLiteral, + Term, + #[precedence(level="2")] #[assoc(side="right")] + "-" => UnOp::Minus .to_expr(l..r, e).into(), + NOT => UnOp::Not .to_expr(l..r, e).into(), + LENGTH => UnOp::Length .to_expr(l..r, e).into(), + ROUND => UnOp::Round .to_expr(l..r, e).into(), + ABS => UnOp::Abs .to_expr(l..r, e).into(), + FLOOR => UnOp::Floor .to_expr(l..r, e).into(), + CEIL => UnOp::Ceil .to_expr(l..r, e).into(), + SQRT => UnOp::Sqrt .to_expr(l..r, e).into(), + SIN => UnOp::Sin .to_expr(l..r, e).into(), + COS => UnOp::Cos .to_expr(l..r, e).into(), + TAN => UnOp::Tan .to_expr(l..r, e).into(), + ASIN => UnOp::Asin .to_expr(l..r, e).into(), + ACOS => UnOp::Acos .to_expr(l..r, e).into(), + ATAN => UnOp::Atan .to_expr(l..r, e).into(), + LN => UnOp::Ln .to_expr(l..r, e).into(), + LOG => UnOp::Log .to_expr(l..r, e).into(), + ANTILN => UnOp::AntiLn .to_expr(l..r, e).into(), + ANTILOG => UnOp::AntiLog.to_expr(l..r, e).into(), + #[precedence(level="3")] #[assoc(side="left")] + "*" => BinOp::Mul .to_expr(l..r, lhs, rhs).into(), + "/" => BinOp::Div .to_expr(l..r, lhs, rhs).into(), + "//" => BinOp::FloorDiv.to_expr(l..r, lhs, rhs).into(), + "%" => BinOp::Mod .to_expr(l..r, lhs, rhs).into(), + #[precedence(level="4")] #[assoc(side="left")] + "+" => BinOp::Add .to_expr(l..r, lhs, rhs).into(), + "-" => BinOp::Sub .to_expr(l..r, lhs, rhs).into(), + #[precedence(level="5")] #[assoc(side="left")] + "<" => BinOp::Lt .to_expr(l..r, lhs, rhs).into(), + "<=" => BinOp::Le .to_expr(l..r, lhs, rhs).into(), + ">" => BinOp::Gt .to_expr(l..r, lhs, rhs).into(), + ">=" => BinOp::Ge .to_expr(l..r, lhs, rhs).into(), + #[precedence(level="6")] #[assoc(side="right")] + "&" => BinOp::Join .to_expr(l..r, lhs, rhs).into(), + #[precedence(level="7")] #[assoc(side="left")] + IN => BinOp::In .to_expr(l..r, lhs, rhs).into(), + "==" => BinOp::Eq .to_expr(l..r, lhs, rhs).into(), + "!=" => BinOp::Ne .to_expr(l..r, lhs, rhs).into(), + #[precedence(level="8")] #[assoc(side="left")] + AND => BinOp::And .to_expr(l..r, lhs, rhs).into(), + #[precedence(level="9")] #[assoc(side="left")] + OR => BinOp::Or .to_expr(l..r, lhs, rhs).into(), +} + +IfExpr: Rrc = { #[precedence(level="1")] Term, #[precedence(level="2")] #[assoc(side="right")] - "-" => UnOp::Minus .to_expr(expr).into(), - NOT => UnOp::Not .to_expr(expr).into(), - LENGTH => UnOp::Length .to_expr(expr).into(), - ROUND => UnOp::Round .to_expr(expr).into(), - ABS => UnOp::Abs .to_expr(expr).into(), - FLOOR => UnOp::Floor .to_expr(expr).into(), - CEIL => UnOp::Ceil .to_expr(expr).into(), - SQRT => UnOp::Sqrt .to_expr(expr).into(), - SIN => UnOp::Sin .to_expr(expr).into(), - COS => UnOp::Cos .to_expr(expr).into(), - TAN => UnOp::Tan .to_expr(expr).into(), - ASIN => UnOp::Asin .to_expr(expr).into(), - ACOS => UnOp::Acos .to_expr(expr).into(), - ATAN => UnOp::Atan .to_expr(expr).into(), - LN => UnOp::Ln .to_expr(expr).into(), - LOG => UnOp::Log .to_expr(expr).into(), - ANTILN => UnOp::AntiLn .to_expr(expr).into(), - ANTILOG => UnOp::AntiLog.to_expr(expr).into(), + "-" => UnOp::Minus .to_expr(l..r, e).into(), + NOT => UnOp::Not .to_expr(l..r, e).into(), + LENGTH => UnOp::Length .to_expr(l..r, e).into(), + ROUND => UnOp::Round .to_expr(l..r, e).into(), + ABS => UnOp::Abs .to_expr(l..r, e).into(), + FLOOR => UnOp::Floor .to_expr(l..r, e).into(), + CEIL => UnOp::Ceil .to_expr(l..r, e).into(), + SQRT => UnOp::Sqrt .to_expr(l..r, e).into(), + SIN => UnOp::Sin .to_expr(l..r, e).into(), + COS => UnOp::Cos .to_expr(l..r, e).into(), + TAN => UnOp::Tan .to_expr(l..r, e).into(), + ASIN => UnOp::Asin .to_expr(l..r, e).into(), + ACOS => UnOp::Acos .to_expr(l..r, e).into(), + ATAN => UnOp::Atan .to_expr(l..r, e).into(), + LN => UnOp::Ln .to_expr(l..r, e).into(), + LOG => UnOp::Log .to_expr(l..r, e).into(), + ANTILN => UnOp::AntiLn .to_expr(l..r, e).into(), + ANTILOG => UnOp::AntiLog.to_expr(l..r, e).into(), #[precedence(level="3")] #[assoc(side="left")] - "*" => BinOp::Mul.to_expr(lhs, rhs).into(), - "/" => BinOp::Div.to_expr(lhs, rhs).into(), - "//" => BinOp::FloorDiv.to_expr(lhs, rhs).into(), - "%" => BinOp::Mod.to_expr(lhs, rhs).into(), + "*" => BinOp::Mul .to_expr(l..r, lhs, rhs).into(), + "/" => BinOp::Div .to_expr(l..r, lhs, rhs).into(), + "//" => BinOp::FloorDiv.to_expr(l..r, lhs, rhs).into(), + "%" => BinOp::Mod .to_expr(l..r, lhs, rhs).into(), #[precedence(level="4")] #[assoc(side="left")] - "+" => BinOp::Add.to_expr(lhs, rhs).into(), - "-" => BinOp::Sub.to_expr(lhs, rhs).into(), + "+" => BinOp::Add .to_expr(l..r, lhs, rhs).into(), + "-" => BinOp::Sub .to_expr(l..r, lhs, rhs).into(), #[precedence(level="5")] #[assoc(side="left")] - "<" => BinOp::Lt.to_expr(lhs, rhs).into(), - "<=" => BinOp::Le.to_expr(lhs, rhs).into(), - ">" => BinOp::Gt.to_expr(lhs, rhs).into(), - ">=" => BinOp::Ge.to_expr(lhs, rhs).into(), + "<" => BinOp::Lt .to_expr(l..r, lhs, rhs).into(), + "<=" => BinOp::Le .to_expr(l..r, lhs, rhs).into(), + ">" => BinOp::Gt .to_expr(l..r, lhs, rhs).into(), + ">=" => BinOp::Ge .to_expr(l..r, lhs, rhs).into(), #[precedence(level="6")] #[assoc(side="right")] - "&" => BinOp::Join.to_expr(lhs, rhs).into(), + "&" => BinOp::Join .to_expr(l..r, lhs, rhs).into(), #[precedence(level="7")] #[assoc(side="left")] - IN => BinOp::In.to_expr(lhs, rhs).into(), - "==" => BinOp::Eq.to_expr(lhs, rhs).into(), - "!=" => BinOp::Ne.to_expr(lhs, rhs).into(), + IN => BinOp::In .to_expr(l..r, lhs, rhs).into(), + "==" => BinOp::Eq .to_expr(l..r, lhs, rhs).into(), + "!=" => BinOp::Ne .to_expr(l..r, lhs, rhs).into(), #[precedence(level="8")] #[assoc(side="left")] - AND => BinOp::And.to_expr(lhs, rhs).into(), + AND => BinOp::And .to_expr(l..r, lhs, rhs).into(), #[precedence(level="9")] #[assoc(side="left")] - OR => BinOp::Or.to_expr(lhs, rhs).into(), + OR => BinOp::Or .to_expr(l..r, lhs, rhs).into(), } Term: Rrc = { "(" ")", - TRUE => Expr::Int(1).into(), - FALSE => Expr::Int(0).into(), - => Expr::Int(value).into(), - => Expr::Int(value).into(), - => Expr::Int(value).into(), - => Expr::Int(value).into(), - => Expr::Float(value).into(), - => Expr::Str(value).into(), - => Expr::Name { name, span: l..r }.into(), - => Expr::Arg { name, span: l..r }.into(), + TRUE => Value::from(1).to_expr(l..r).into(), + FALSE => Value::from(0).to_expr(l..r).into(), + => Value::from(v).to_expr(l..r).into(), + => Value::from(v).to_expr(l..r).into(), + => Value::from(v).to_expr(l..r).into(), + => Value::from(v).to_expr(l..r).into(), + => Value::from(v).to_expr(l..r).into(), + => Value::from(v).to_expr(l..r).into(), + => Expr::Name (Name::Name { name: n, span: l..r }).into(), + => Expr::Arg (Name::Name { name: n, span: l..r }).into(), "(" ")" =>? match Repr::from_shape(&name, args.len()) { Some(repr) => Ok(Expr::Repr { repr, span: l..r, args }.into()), - None => Err(ParseError::User { error: DiagnosticKind::UnrecognizedReporter(name).to_diagnostic(l..r) }), + None => Err(ParseError::User { error: Diagnostic { kind: DiagnosticKind::UnrecognizedReporter(name), span: l..r } }), }, - "[" "]" => BinOp::Of.to_expr(e, i).into(), - "." => - Expr::EnumVariant { - enum_name, - enum_span: nl..nr, - variant_name, - variant_span: vl..vr - }.into(), + "[" "]" => BinOp::Of.to_expr(l..r, e, i).into(), + "." => Expr::Dot { lhs: v, rhs: n, rhs_span: l..r }.into(), } -Exprs: Vec> = >; +StructLiteral: Rrc = "{" > "}" => Expr::StructLiteral { name: n, span: l..r, fields: f }.into(); +StructLiteralField: StructLiteralField = "=" => StructLiteralField { name: n, span: l..r, value: v }; + +Name: Name = { + , + , +} + +#[inline] +NameName: Name = => Name::Name { name: n, span: l..r }; +#[inline] +NameDotName: Name = "." => Name::DotName { lhs: n, lhs_span: l..r, rhs: f, rhs_span: lf..rf }; SpannedComma: Vec<(T, Span)> = { > "," => { @@ -225,6 +293,7 @@ extern { HEX => Token::Hex(), FLOAT => Token::Float(), STR => Token::Str(), + CMD => Token::Cmd(), COSTUMES => Token::Costumes, SOUNDS => Token::Sounds, LOCAL => Token::Local, @@ -306,7 +375,9 @@ extern { OF => Token::Of, AS => Token::As, ENUM => Token::Enum, + STRUCT => Token::Struct, TRUE => Token::True, FALSE => Token::False, + LIST => Token::List, } } diff --git a/src/visitor.rs b/src/visitor.rs new file mode 100644 index 0000000..94d0656 --- /dev/null +++ b/src/visitor.rs @@ -0,0 +1,2 @@ +pub mod pass0; +pub mod pass1; diff --git a/src/visitor/pass0.rs b/src/visitor/pass0.rs new file mode 100644 index 0000000..463d5e6 --- /dev/null +++ b/src/visitor/pass0.rs @@ -0,0 +1,134 @@ +use fxhash::FxHashMap; +use smol_str::SmolStr; + +use crate::ast::*; + +struct V<'a> { + locals: Option<&'a mut FxHashMap>, + vars: &'a mut FxHashMap, + global_vars: Option<&'a mut FxHashMap>, +} + +pub fn visit_project(project: &mut Project) { + visit_sprite(&mut project.stage, None); + for sprite in project.sprites.values_mut() { + visit_sprite(sprite, Some(&mut project.stage)); + } +} + +fn visit_sprite(sprite: &mut Sprite, mut stage: Option<&mut Sprite>) { + visit_costumes(&mut sprite.costumes); + for proc in sprite.procs.values_mut() { + visit_stmts( + &mut proc.body, + &mut V { + locals: Some(&mut proc.locals), + vars: &mut sprite.vars, + global_vars: stage.as_mut().map(|stage| &mut stage.vars), + }, + ); + } + for event in &mut sprite.events { + visit_stmts( + &mut event.body, + &mut V { + locals: None, + vars: &mut sprite.vars, + global_vars: stage.as_mut().map(|stage| &mut stage.vars), + }, + ); + } +} + +fn visit_costumes(new: &mut Vec) { + let old: Vec = std::mem::take(new); + for costume in old { + if let Some(suffix) = costume.name.strip_prefix("@ascii/") { + new.extend((' '..='~').map(|ch| Costume { + name: format!("{suffix}{ch}").into(), + path: costume.path.clone(), + span: costume.span.clone(), + })); + } else { + new.push(costume); + } + } +} + +fn visit_stmts(stmts: &mut Vec, v: &mut V) { + for stmt in stmts { + visit_stmt(stmt, v); + } +} + +fn visit_stmt(stmt: &mut Stmt, v: &mut V) { + match stmt { + Stmt::Repeat { body, .. } => visit_stmts(body, v), + Stmt::Forever { body, .. } => visit_stmts(body, v), + Stmt::Branch { + if_body, else_body, .. + } => { + visit_stmts(if_body, v); + visit_stmts(else_body, v) + } + Stmt::Until { body, .. } => visit_stmts(body, v), + Stmt::SetVar { + name, + type_, + is_local, + .. + } => { + let basename = name.basename(); + let var = Var { + name: basename.clone(), + span: name.span(), + type_: type_.clone(), + }; + if *is_local { + if let Some(locals) = &mut v.locals { + if let Some(existing_declaration) = locals.get(basename) { + if existing_declaration.type_.is_value() { + locals.insert(basename.clone(), var); + } + } else { + locals.insert(basename.clone(), var); + } + } + return; + } + if v.locals + .as_ref() + .is_some_and(|locals| locals.contains_key(basename)) + { + return; + } + if v.global_vars + .as_ref() + .is_some_and(|global_vars| global_vars.contains_key(basename)) + { + return; + } + if let Some(existing_declaration) = v.vars.get(basename) { + // This condition ensures that variables with a specific type (e.g., a struct type) are not overwritten + // by a previous statement that didn't specify a type (which defaults to type `Value`). + // In this context, variables don't need to be explicitly declared if the type is `Value`. + // The syntax for setting variables is as follows: + // - For `Value` type: `variable_name = value;` + // - For a specific struct type: `typeName variable_name = value;` + // + // Since the visitor processes every variable assignment statement, this check ensures that if an + // existing variable has a specific type (not `Value`), it is preserved when a new statement tries to + // reassign it without a type (defaulting to `Value`). Only variables that are of type `Value` can be + // overwritten by the new assignment. + + // TODO: Make redeclaration of variables with different struct types an error. + if existing_declaration.type_.is_value() { + v.vars.insert(basename.clone(), var); + } + } else { + v.vars.insert(basename.clone(), var); + } + } + _ => (), + } +} diff --git a/src/visitor/pass1.rs b/src/visitor/pass1.rs new file mode 100644 index 0000000..713b537 --- /dev/null +++ b/src/visitor/pass1.rs @@ -0,0 +1,667 @@ +use fxhash::FxHashMap; +use log::info; +use logos::Span; +use smol_str::SmolStr; + +use crate::{ + ast::*, + blocks::{BinOp, UnOp}, + codegen::sb3::D, + diagnostic::{DiagnosticKind, SpriteDiagnostics}, + misc::Rrc, +}; + +#[derive(Copy, Clone)] +struct S<'a> { + args: Option<&'a Vec>, + local_vars: Option<&'a FxHashMap>, + vars: &'a FxHashMap, + lists: &'a FxHashMap, + enums: &'a FxHashMap, + structs: &'a FxHashMap, + global_vars: Option<&'a FxHashMap>, + global_lists: Option<&'a FxHashMap>, + global_enums: Option<&'a FxHashMap>, + global_structs: Option<&'a FxHashMap>, +} + +impl<'a> S<'a> { + fn get_var(&self, name: &str) -> Option<&Var> { + self.local_vars + .and_then(|local_vars| local_vars.get(name)) + .or_else(|| self.vars.get(name)) + .or_else(|| { + self.global_vars + .and_then(|global_vars| global_vars.get(name)) + }) + } + + fn get_list(&self, name: &str) -> Option<&List> { + self.lists.get(name).or_else(|| { + self.global_lists + .and_then(|global_lists| global_lists.get(name)) + }) + } + + fn get_struct(&self, name: &str) -> Option<&Struct> { + self.structs.get(name).or_else(|| { + self.global_structs + .and_then(|global_structs| global_structs.get(name)) + }) + } +} + +pub fn visit_project( + project: &mut Project, + stage_diagnostics: &mut SpriteDiagnostics, + sprites_diagnostics: &mut FxHashMap, +) { + visit_sprite(&mut project.stage, None, stage_diagnostics); + for (sprite_name, sprite) in &mut project.sprites { + visit_sprite( + sprite, + Some(&project.stage), + sprites_diagnostics.get_mut(sprite_name).unwrap(), + ); + } +} + +fn visit_sprite(sprite: &mut Sprite, stage: Option<&Sprite>, d: D) { + for proc in sprite.procs.values_mut() { + visit_stmts( + &mut proc.body, + S { + args: Some(&proc.args), + local_vars: Some(&proc.locals), + vars: &sprite.vars, + lists: &sprite.lists, + enums: &sprite.enums, + structs: &sprite.structs, + global_vars: stage.map(|stage| &stage.vars), + global_lists: stage.map(|stage| &stage.lists), + global_enums: stage.map(|stage| &stage.enums), + global_structs: stage.map(|stage| &stage.structs), + }, + d, + ); + } + for event in &mut sprite.events { + visit_stmts( + &mut event.body, + S { + args: None, + local_vars: None, + vars: &sprite.vars, + lists: &sprite.lists, + enums: &sprite.enums, + structs: &sprite.structs, + global_vars: stage.map(|stage| &stage.vars), + global_lists: stage.map(|stage| &stage.lists), + global_enums: stage.map(|stage| &stage.enums), + global_structs: stage.map(|stage| &stage.structs), + }, + d, + ); + } +} + +fn visit_stmts(stmts: &mut Vec, s: S, d: D) { + for stmt in &mut *stmts { + visit_stmt(stmt, s, d); + } + let mut i = 0; + while i < stmts.len() { + let replace = match &stmts[i] { + Stmt::SetVar { + name, + value, + type_, + is_local, + } => visit_stmt_set_var(s, d, name, value, type_, is_local), + Stmt::SetListIndex { name, index, value } => { + visit_stmt_list_set(s, d, name, index, value) + } + Stmt::AddToList { name, value } => visit_stmt_list_add(s, d, name, value), + Stmt::DeleteList(name) => visit_stmt_delete_list(s, d, name), + Stmt::DeleteListIndex { name, index } => { + visit_stmt_delete_list_index(s, d, name, index) + } + Stmt::InsertAtList { name, index, value } => { + visit_stmt_insert_at_list(s, d, name, index, value) + } + _ => None, + }; + if let Some(replace) = replace { + let len = replace.len(); + stmts.remove(i); + for replace in replace.into_iter().rev() { + stmts.insert(i, replace); + } + i += len - 1; + } + i += 1; + } +} + +fn visit_stmt(stmt: &mut Stmt, s: S, d: D) { + match stmt { + Stmt::Repeat { times, body } => { + visit_expr(times, s, d); + visit_stmts(body, s, d); + } + Stmt::Forever { body, span: _ } => { + visit_stmts(body, s, d); + } + Stmt::Branch { + cond, + if_body, + else_body, + } => { + visit_expr(cond, s, d); + visit_stmts(if_body, s, d); + visit_stmts(else_body, s, d); + } + Stmt::Until { cond, body } => { + visit_expr(cond, s, d); + visit_stmts(body, s, d); + } + Stmt::SetVar { + name: _, + value, + type_: _, + is_local: _, + } => { + visit_expr(value, s, d); + } + Stmt::Show(_) => {} + Stmt::Hide(_) => {} + Stmt::AddToList { name: _, value } => { + visit_expr(value, s, d); + } + Stmt::DeleteList(_) => {} + Stmt::DeleteListIndex { name: _, index } => { + visit_expr(index, s, d); + } + Stmt::InsertAtList { + name: _, + index, + value, + } => { + visit_expr(value, s, d); + visit_expr(index, s, d); + } + Stmt::SetListIndex { + name: _, + index, + value, + } => { + visit_expr(value, s, d); + visit_expr(index, s, d); + } + + Stmt::Block { + block: _, + span: _, + args, + } => { + for arg in args { + visit_expr(arg, s, d); + } + } + Stmt::ProcCall { + name: _, + span: _, + args, + } => { + for arg in args { + visit_expr(arg, s, d); + } + } + } +} + +fn visit_expr(expr: &mut Rrc, s: S, d: D) { + let replace: Option> = match &mut *expr.borrow_mut() { + Expr::Value { value: _, span: _ } => None, + Expr::Name(name) => visit_expr_name(s, name), + Expr::Arg(name) => visit_expr_arg(s, name), + Expr::Dot { lhs, rhs, rhs_span } => { + visit_expr(lhs, s, d); + visit_expr_dot(d, lhs, rhs, rhs_span) + } + Expr::Repr { + repr: _, + span: _, + args, + } => { + for arg in args { + visit_expr(arg, s, d); + } + None + } + Expr::UnOp { + op: _, + span: _, + opr, + } => { + visit_expr(opr, s, d); + None + } + Expr::BinOp { op, span, lhs, rhs } => { + visit_expr(lhs, s, d); + visit_expr(rhs, s, d); + match op { + BinOp::Add => None, + BinOp::Sub => None, + BinOp::Mul => None, + BinOp::Div => None, + BinOp::Mod => None, + BinOp::Lt => None, + BinOp::Gt => None, + BinOp::Eq => None, + BinOp::And => None, + BinOp::Or => None, + BinOp::Join => None, + BinOp::In => None, + BinOp::Of => visit_expr_bin_op_of(s, span, lhs, rhs), + BinOp::Le => Some( + UnOp::Not + .to_expr( + span.clone(), + BinOp::Gt + .to_expr(span.clone(), lhs.clone(), rhs.clone()) + .into(), + ) + .into(), + ), + BinOp::Ge => Some( + UnOp::Not + .to_expr( + span.clone(), + BinOp::Lt + .to_expr(span.clone(), lhs.clone(), rhs.clone()) + .into(), + ) + .into(), + ), + BinOp::Ne => Some( + UnOp::Not + .to_expr( + span.clone(), + BinOp::Eq + .to_expr(span.clone(), lhs.clone(), rhs.clone()) + .into(), + ) + .into(), + ), + BinOp::FloorDiv => Some( + UnOp::Floor + .to_expr( + span.clone(), + BinOp::Div + .to_expr(span.clone(), lhs.clone(), rhs.clone()) + .into(), + ) + .into(), + ), + } + } + Expr::StructLiteral { + name: _, + span: _, + fields, + } => { + for field in fields { + visit_expr(&mut field.value, s, d); + } + None + } + }; + if let Some(replace) = replace { + *expr = replace; + } +} + +fn visit_expr_name(s: S, name: &Name) -> Option> { + info!(target: "pass1", "visit_expr_name {name:#?}"); + if name.fieldname().is_some() { + return None; + } + let basename = name.basename(); + let span = name.span(); + let var = &s.get_var(basename)?; + info!(target: "pass1", "var {var:#?}"); + let (type_name, type_span) = var.type_.struct_()?; + info!(target: "pass1", "type_name {type_name:#?}"); + let struct_ = s.get_struct(type_name)?; + info!(target: "pass1", "struct_ {struct_:#?}"); + Some( + Expr::StructLiteral { + name: type_name.clone(), + span: type_span.clone(), + fields: struct_ + .fields + .iter() + .map(|field| StructLiteralField { + name: field.name.clone(), + span: field.span.clone(), + value: Expr::Name(Name::DotName { + lhs: var.name.clone(), + lhs_span: span.clone(), + rhs: field.name.clone(), + rhs_span: field.span.clone(), + }) + .into(), + }) + .collect(), + } + .into(), + ) +} + +fn visit_expr_arg(s: S, name: &Name) -> Option> { + if name.fieldname().is_some() { + return None; + } + let basename = name.basename(); + let span = name.span(); + let arg = s.args?.iter().find(|arg| &arg.name == basename)?; + let (type_name, type_span) = arg.type_.struct_()?; + let struct_ = s.get_struct(type_name)?; + Some( + Expr::StructLiteral { + name: type_name.clone(), + span: type_span.clone(), + fields: struct_ + .fields + .iter() + .map(|field| StructLiteralField { + name: field.name.clone(), + span: field.span.clone(), + value: Expr::Arg(Name::DotName { + lhs: arg.name.clone(), + lhs_span: span.clone(), + rhs: field.name.clone(), + rhs_span: field.span.clone(), + }) + .into(), + }) + .collect(), + } + .into(), + ) +} + +fn visit_expr_bin_op_of(s: S, span: &Span, lhs: &Rrc, rhs: &Rrc) -> Option> { + let Expr::Name(Name::Name { name, span }) = &*lhs.borrow() else { + return None; + }; + let list = s.get_list(name)?; + let (type_name, type_span) = list.type_.struct_()?; + let struct_ = s.get_struct(type_name)?; + Some( + Expr::StructLiteral { + name: type_name.clone(), + span: type_span.clone(), + fields: struct_ + .fields + .iter() + .map(|field| StructLiteralField { + name: field.name.clone(), + span: field.span.clone(), + value: BinOp::Of + .to_expr( + span.clone(), + Expr::Name(Name::DotName { + lhs: name.clone(), + lhs_span: span.clone(), + rhs: field.name.clone(), + rhs_span: field.span.clone(), + }) + .into(), + rhs.clone(), + ) + .into(), + }) + .collect(), + } + .into(), + ) +} + +fn visit_expr_dot(d: D, lhs: &Rrc, rhs: &SmolStr, rhs_span: &Span) -> Option> { + let Expr::StructLiteral { + name: lhs_name, + span: _, + fields, + } = &*lhs.borrow() + else { + return None; + }; + let Some(field) = fields.iter().find(|field| &field.name == rhs) else { + d.report( + DiagnosticKind::StructDoesNotHaveField { + type_name: lhs_name.clone(), + field_name: rhs.clone(), + }, + rhs_span, + ); + return None; + }; + Some(field.value.clone()) +} + +fn visit_stmt_set_var( + s: S, + d: D, + name: &Name, + value: &Rrc, + _type: &Type, + _is_local: &bool, +) -> Option> { + let expr = &*value.borrow(); + let struct_literal_fields = get_struct_literal_for_type(s, d, name, expr, |basename| { + s.get_var(basename).map(|var| &var.type_) + })?; + Some( + struct_literal_fields + .iter() + .map(|struct_literal_field| Stmt::SetVar { + name: Name::DotName { + lhs: name.basename().clone(), + lhs_span: name.basespan().clone(), + rhs: struct_literal_field.name.clone(), + rhs_span: struct_literal_field.span.clone(), + }, + value: struct_literal_field.value.clone(), + type_: Type::Value, + is_local: false, + }) + .collect(), + ) +} + +fn visit_stmt_list_set( + s: S, + d: D, + name: &Name, + index: &Rrc, + value: &Rrc, +) -> Option> { + let expr = &*value.borrow(); + let struct_literal_fields = get_struct_literal_for_type(s, d, name, expr, |basename| { + s.get_list(basename).map(|list| &list.type_) + })?; + Some( + struct_literal_fields + .iter() + .map(|struct_literal_field| Stmt::SetListIndex { + name: Name::DotName { + lhs: name.basename().clone(), + lhs_span: name.basespan().clone(), + rhs: struct_literal_field.name.clone(), + rhs_span: struct_literal_field.span.clone(), + }, + index: index.clone(), + value: struct_literal_field.value.clone(), + }) + .collect(), + ) +} + +fn visit_stmt_list_add(s: S, d: D, name: &Name, value: &Rrc) -> Option> { + let expr = &*value.borrow(); + let struct_literal_fields = get_struct_literal_for_type(s, d, name, expr, |basename| { + s.get_list(basename).map(|list| &list.type_) + })?; + Some( + struct_literal_fields + .iter() + .map(|struct_literal_field| Stmt::AddToList { + name: Name::DotName { + lhs: name.basename().clone(), + lhs_span: name.basespan().clone(), + rhs: struct_literal_field.name.clone(), + rhs_span: struct_literal_field.span.clone(), + }, + value: struct_literal_field.value.clone(), + }) + .collect(), + ) +} + +fn visit_stmt_delete_list(s: S, d: D, name: &Name) -> Option> { + if name.fieldname().is_some() { + return None; + } + let basename = name.basename(); + let type_ = &s.get_list(basename)?.type_; + let (type_name, _) = type_.struct_()?; + let struct_ = s.get_struct(type_name)?; + Some( + struct_ + .fields + .iter() + .map(|struct_field| { + Stmt::DeleteList(Name::DotName { + lhs: name.basename().clone(), + lhs_span: name.basespan().clone(), + rhs: struct_field.name.clone(), + rhs_span: struct_field.span.clone(), + }) + }) + .collect(), + ) +} + +fn visit_stmt_insert_at_list( + s: S, + d: D, + name: &Name, + index: &Rrc, + value: &Rrc, +) -> Option> { + let expr = &*value.borrow(); + let struct_literal_fields = get_struct_literal_for_type(s, d, name, expr, |basename| { + s.get_list(basename).map(|list| &list.type_) + })?; + Some( + struct_literal_fields + .iter() + .map(|struct_literal_field| Stmt::InsertAtList { + name: Name::DotName { + lhs: name.basename().clone(), + lhs_span: name.basespan().clone(), + rhs: struct_literal_field.name.clone(), + rhs_span: struct_literal_field.span.clone(), + }, + index: index.clone(), + value: struct_literal_field.value.clone(), + }) + .collect(), + ) +} + +fn visit_stmt_delete_list_index(s: S, _d: D, name: &Name, index: &Rrc) -> Option> { + if name.fieldname().is_some() { + return None; + } + let basename = name.basename(); + let type_ = &s.get_list(basename)?.type_; + let (type_name, _) = type_.struct_()?; + let struct_ = s.get_struct(type_name)?; + Some( + struct_ + .fields + .iter() + .map(|struct_field| Stmt::DeleteListIndex { + name: Name::DotName { + lhs: name.basename().clone(), + lhs_span: name.basespan().clone(), + rhs: struct_field.name.clone(), + rhs_span: struct_field.span.clone(), + }, + index: index.clone(), + }) + .collect(), + ) +} + +fn get_struct_literal_for_type<'a, T>( + s: S, + d: D, + name: &Name, + expr: &'a Expr, + get_type: T, +) -> Option<&'a [StructLiteralField]> +where + T: FnOnce(&str) -> Option<&'a Type>, +{ + if name.fieldname().is_some() { + return None; + } + let basename = name.basename(); + let basespan = name.basespan(); + let type_ = get_type(basename)?; + let (type_name, type_span) = type_.struct_()?; + let struct_ = s.get_struct(type_name)?; + let Expr::StructLiteral { + name: struct_literal_name, + span: struct_literal_span, + fields: struct_literal_fields, + } = expr + else { + d.report( + DiagnosticKind::TypeMismatch { + expected: type_.clone(), + given: Type::Value, + }, + &basespan, + ); + return None; + }; + let Some(value_struct) = s.get_struct(struct_literal_name) else { + d.report( + DiagnosticKind::UnrecognizedStruct(struct_literal_name.clone()), + struct_literal_span, + ); + return None; + }; + if struct_.name != value_struct.name { + d.report( + DiagnosticKind::TypeMismatch { + expected: Type::Struct { + name: struct_.name.clone(), + span: type_span.clone(), + }, + given: Type::Struct { + name: value_struct.name.clone(), + span: struct_literal_span.clone(), + }, + }, + &basespan, + ); + return None; + } + Some(struct_literal_fields) +} diff --git a/tests/operators/main.gs b/tests/operators/main.gs index 4e29585..29eb946 100644 --- a/tests/operators/main.gs +++ b/tests/operators/main.gs @@ -1,7 +1,6 @@ costumes "blank.svg"; onflag { - delete my_list; lhs = 1; rhs = 2; say lhs + rhs; @@ -39,22 +38,4 @@ onflag { say log lhs; say antiln lhs; say antilog lhs; - lhs++; - lhs--; - lhs += rhs; - lhs -= rhs; - lhs *= rhs; - lhs /= rhs; - lhs //= rhs; - lhs %= rhs; - lhs &= "suffix"; - my_list[0] += 1; - my_list[0] -= 1; - my_list[0] *= 1; - my_list[0] /= 1; - my_list[0] //= 1; - my_list[0] %= 1; - my_list[0] &= "suffix"; - my_list[0]++; - my_list[0]--; } diff --git a/tests/vars/main.gs b/tests/vars/main.gs index 4e27c8a..726eeda 100644 --- a/tests/vars/main.gs +++ b/tests/vars/main.gs @@ -9,6 +9,7 @@ proc main { var -= local_var; var *= local_var; var /= local_var; + var //= local_var; var %= local_var; var &= local_var; } diff --git a/tools/bun.lockb b/tools/bun.lockb new file mode 100755 index 0000000..9d981c7 Binary files /dev/null and b/tools/bun.lockb differ diff --git a/tools/package.json b/tools/package.json index c7061e0..8bba4cb 100644 --- a/tools/package.json +++ b/tools/package.json @@ -1,6 +1,6 @@ { - "dependencies": { - "ajv": "^8.12.0", - "better-ajv-errors": "^1.2.0" - } + "dependencies": { + "ajv": "^8.12.0", + "better-ajv-errors": "^1.2.0" + } } diff --git a/tools/run b/tools/run index 75c03d8..7c35d96 100755 --- a/tools/run +++ b/tools/run @@ -18,7 +18,7 @@ case $1 in done ;; "compile") - cargo run -- build -i playground + RUST_LOG=info cargo run -- build -i playground unzip -o playground/playground.sb3 project.json -d playground python -m json.tool --indent 4 playground/project.json playground/project.json node tools/sb3.js playground/project.json diff --git a/tools/sb3.js b/tools/sb3.js index e1d06fd..c166146 100644 --- a/tools/sb3.js +++ b/tools/sb3.js @@ -1,24 +1,24 @@ -"use strict"; -const fs = require("fs"); -const definitions = require("./sb3_definitions.json"); -const schema = require("./sb3_schema.json"); -const betterAjvErrors = require("better-ajv-errors").default; -const Ajv = require("ajv"); -const ajv = new Ajv({ strict: false }); -ajv.addSchema(definitions); -let jsonData; +'use strict' +const fs = require('fs') +const definitions = require('./sb3_definitions.json') +const schema = require('./sb3_schema.json') +const betterAjvErrors = require('better-ajv-errors').default +const Ajv = require('ajv') +const ajv = new Ajv({ strict: false }) +ajv.addSchema(definitions) +let jsonData if (process.argv[2]) { - jsonData = fs.readFileSync(process.argv[2]).toString(); + jsonData = fs.readFileSync(process.argv[2]).toString() } else { - // stdio - jsonData = fs.readFileSync(0).toString(); + // stdio + jsonData = fs.readFileSync(0).toString() } -const data = JSON.parse(jsonData); -const validate = ajv.compile(schema); -const valid = validate(data); +const data = JSON.parse(jsonData) +const validate = ajv.compile(schema) +const valid = validate(data) if (!valid) { - const output = betterAjvErrors(ajv.schema, data, validate.errors, { - json: jsonData, - }); - console.log(output); + const output = betterAjvErrors(ajv.schema, data, validate.errors, { + json: jsonData, + }) + console.log(output) }