diff --git a/crates/yuck/Cargo.lock b/crates/yuck/Cargo.lock new file mode 100644 index 0000000..b720e0f --- /dev/null +++ b/crates/yuck/Cargo.lock @@ -0,0 +1,848 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "beef" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409" + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "console" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size", + "winapi", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "ctor" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "ena" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +dependencies = [ + "log", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "eww_config" +version = "0.1.0" +dependencies = [ + "anyhow", + "codespan-reporting", + "derive_more", + "insta", + "itertools", + "lalrpop", + "lalrpop-util", + "lazy_static", + "maplit", + "pretty_assertions", + "regex", + "serde", + "serde_json", + "simplexpr", + "smart-default", + "strum", + "thiserror", +] + +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "insta" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1b21a2971cea49ca4613c0e9fe8225ecaf5de64090fddc6002284726e9244" +dependencies = [ + "console", + "lazy_static", + "ron", + "serde", + "serde_json", + "serde_yaml", + "similar", + "uuid", +] + +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "lalrpop" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" +dependencies = [ + "ascii-canvas", + "atty", + "bit-set", + "diff", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" +dependencies = [ + "regex", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "logos" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax", + "syn", + "utf8-ranges", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi", +] + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "pretty_assertions" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "ron" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064ea8613fb712a19faf920022ec8ddf134984f100090764a4e1d768f3827f1f" +dependencies = [ + "base64", + "bitflags", + "serde", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] +name = "similar" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" + +[[package]] +name = "simplexpr" +version = "0.1.0" +dependencies = [ + "itertools", + "lalrpop", + "lalrpop-util", + "logos", + "maplit", + "regex", + "serde", + "serde_json", + "strum", + "thiserror", +] + +[[package]] +name = "siphasher" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" + +[[package]] +name = "smart-default" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "string_cache" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strum" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "utf8-ranges" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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 = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/crates/yuck/Cargo.toml b/crates/yuck/Cargo.toml new file mode 100644 index 0000000..81f54f2 --- /dev/null +++ b/crates/yuck/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "eww_config" +version = "0.1.0" +authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] +edition = "2018" + + +build = "build.rs" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lalrpop-util = "0.19.5" +regex = "1" +itertools = "0.10" +thiserror = "1.0" +maplit = "1.0" +codespan-reporting = "0.11" + +derive_more = "0.99" +smart-default = "0.6" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" +lazy_static = "1.4" +pretty_assertions = "0.7" + +strum = { version = "0.21", features = ["derive"] } +anyhow = "1" + + +simplexpr = { path = "../../projects/simplexpr" } + +[build-dependencies] +lalrpop = "0.19.5" + +[dev-dependencies] +insta = { version = "1.7", features = ["ron"]} diff --git a/crates/yuck/build.rs b/crates/yuck/build.rs new file mode 100644 index 0000000..57684be --- /dev/null +++ b/crates/yuck/build.rs @@ -0,0 +1,4 @@ +extern crate lalrpop; +fn main() { + lalrpop::process_root().unwrap(); +} diff --git a/crates/yuck/examples/errors.rs b/crates/yuck/examples/errors.rs new file mode 100644 index 0000000..a5b26ba --- /dev/null +++ b/crates/yuck/examples/errors.rs @@ -0,0 +1,30 @@ +// use eww_config::{ +// format_diagnostic::ToDiagnostic, +// parser::{ast::*, from_ast::FromAst}, +//}; + +fn main() { + // let mut files = codespan_reporting::files::SimpleFiles::new(); + + // let input = r#" + //(heyho ; :foo { "foo \" } bar " } + //; :baz {(foo == bar ? 12.2 : 12)} + //(foo) + //(defwidget foo [something bla] "foo") + //(baz))"#; + + // let file_id = files.add("foo.eww", input); + // let ast = eww_config::parser::parse_string(file_id, input); + // match ast.and_then(eww_config::parser::from_ast::Element::::from_ast) { + // Ok(ast) => { + // println!("{:?}", ast); + //} + // Err(err) => { + // dbg!(&err); + // let diag = err.to_diagnostic(); + // use codespan_reporting::term; + // let config = term::Config::default(); + // let mut writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Always); + // term::emit(&mut writer, &config, &files, &diag).unwrap(); + //} +} diff --git a/crates/yuck/examples/validation.rs b/crates/yuck/examples/validation.rs new file mode 100644 index 0000000..f2cca38 --- /dev/null +++ b/crates/yuck/examples/validation.rs @@ -0,0 +1,40 @@ +use eww_config::{ + config::{widget_definition::WidgetDefinition, widget_use::WidgetUse, *}, + error::AstError, + format_diagnostic::ToDiagnostic, + parser::from_ast::FromAst, +}; + +fn main() { + let mut files = codespan_reporting::files::SimpleFiles::new(); + + let input_use = r#" + (foo :something 12 + :bla "bruh" + "some text") + "#; + let input_def = r#" + (defwidget foo [something bla] "foo") + "#; + + let file_id_use = files.add("use.eww", input_use); + let file_id_def = files.add("def.eww", input_def); + let parsed_use = WidgetUse::from_ast(eww_config::parser::parse_string(file_id_use, input_use).unwrap()).unwrap(); + let parsed_def = WidgetDefinition::from_ast(eww_config::parser::parse_string(file_id_def, input_def).unwrap()).unwrap(); + let defs = maplit::hashmap! { + "foo".to_string() => parsed_def, + }; + match validate::validate(&defs, &parsed_use) { + Ok(ast) => { + println!("{:?}", ast); + } + Err(err) => { + let err = AstError::ValidationError(err); + let diag = err.to_diagnostic(); + use codespan_reporting::term; + let config = term::Config::default(); + let mut writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Always); + term::emit(&mut writer, &config, &files, &diag).unwrap(); + } + } +} diff --git a/crates/yuck/rust-toolchain b/crates/yuck/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/crates/yuck/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/crates/yuck/rustfmt.toml b/crates/yuck/rustfmt.toml new file mode 100644 index 0000000..edce9c8 --- /dev/null +++ b/crates/yuck/rustfmt.toml @@ -0,0 +1,14 @@ +unstable_features = true +fn_single_line = false +max_width = 130 +reorder_impl_items = true +merge_imports = true +normalize_comments = true +use_field_init_shorthand = true +#wrap_comments = true +combine_control_expr = false +condense_wildcard_suffixes = true +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true +use_small_heuristics = "Max" diff --git a/crates/yuck/src/config/attributes.rs b/crates/yuck/src/config/attributes.rs new file mode 100644 index 0000000..b2e668d --- /dev/null +++ b/crates/yuck/src/config/attributes.rs @@ -0,0 +1,125 @@ +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, +}; + +use simplexpr::{ + dynval::{DynVal, FromDynVal}, + eval::EvalError, + SimplExpr, +}; + +use crate::{ + error::AstError, + parser::{ + ast::{Ast, Span}, + from_ast::FromAst, + }, + value::AttrName, +}; + +#[derive(Debug, thiserror::Error)] +pub enum AttrError { + #[error("Missing required attribute {0}")] + MissingRequiredAttr(Span, AttrName), + + #[error("{1}")] + EvaluationError(Span, EvalError), + + #[error("{1}")] + Other(Span, Box), +} + +impl AttrError { + pub fn span(&self) -> Span { + match self { + AttrError::MissingRequiredAttr(span, _) => *span, + AttrError::EvaluationError(span, _) => *span, + AttrError::Other(span, _) => *span, + } + } +} + +#[derive(Debug)] +pub struct UnusedAttrs { + definition_span: Span, + attrs: Vec<(Span, AttrName)>, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct AttrEntry { + pub key_span: Span, + pub value: Ast, +} + +impl AttrEntry { + pub fn new(key_span: Span, value: Ast) -> AttrEntry { + AttrEntry { key_span, value } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] +pub struct Attributes { + pub span: Span, + pub attrs: HashMap, +} + +impl Attributes { + pub fn new(span: Span, attrs: HashMap) -> Self { + Attributes { span, attrs } + } + + pub fn ast_required(&mut self, key: &str) -> Result { + let key = AttrName(key.to_string()); + match self.attrs.remove(&key) { + Some(AttrEntry { key_span, value }) => T::from_ast(value), + None => Err(AttrError::MissingRequiredAttr(self.span, key.clone()).into()), + } + } + + pub fn ast_optional(&mut self, key: &str) -> Result, AstError> { + match self.attrs.remove(&AttrName(key.to_string())) { + Some(AttrEntry { key_span, value }) => T::from_ast(value).map(Some), + None => Ok(None), + } + } + + pub fn primitive_required(&mut self, key: &str) -> Result + where + E: std::error::Error + 'static, + T: FromDynVal, + { + let ast: SimplExpr = self.ast_required(&key)?; + Ok(ast + .eval_no_vars() + .map_err(|err| AttrError::EvaluationError(ast.span().into(), err))? + .read_as() + .map_err(|e| AttrError::Other(ast.span().into(), Box::new(e)))?) + } + + pub fn primitive_optional(&mut self, key: &str) -> Result, AstError> + where + E: std::error::Error + 'static, + T: FromDynVal, + { + let ast: SimplExpr = match self.ast_optional(key)? { + Some(ast) => ast, + None => return Ok(None), + }; + Ok(Some( + ast.eval_no_vars() + .map_err(|err| AttrError::EvaluationError(ast.span().into(), err))? + .read_as() + .map_err(|e| AttrError::Other(ast.span().into(), Box::new(e)))?, + )) + } + + /// Consumes the attributes to return a list of unused attributes which may be used to emit a warning. + /// TODO actually use this and implement warnings,... lol + pub fn get_unused(self, definition_span: Span) -> UnusedAttrs { + UnusedAttrs { + definition_span, + attrs: self.attrs.into_iter().map(|(k, v)| (v.key_span.to(v.value.span().into()), k)).collect(), + } + } +} diff --git a/crates/yuck/src/config/backend_window_options.rs b/crates/yuck/src/config/backend_window_options.rs new file mode 100644 index 0000000..0cc0a9e --- /dev/null +++ b/crates/yuck/src/config/backend_window_options.rs @@ -0,0 +1,124 @@ +use std::str::FromStr; + +use anyhow::*; + +use crate::{ + enum_parse, + error::AstResult, + parser::{ + ast::{Ast, Span}, + ast_iterator::AstIterator, + from_ast::FromAstElementContent, + }, + value::NumWithUnit, +}; + +use super::{attributes::Attributes, window_definition::EnumParseError}; + +pub type BackendWindowOptions = X11WindowOptions; + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct X11WindowOptions { + pub wm_ignore: bool, + pub sticky: bool, + pub window_type: EwwWindowType, + pub struts: StrutDefinition, +} + +impl X11WindowOptions { + pub fn from_attrs(attrs: &mut Attributes) -> AstResult { + let struts = attrs.ast_optional("reserve")?; + let window_type = attrs.primitive_optional("windowtype")?; + Ok(X11WindowOptions { + wm_ignore: attrs.primitive_optional("wm-ignore")?.unwrap_or(window_type.is_none() && struts.is_none()), + window_type: window_type.unwrap_or_default(), + sticky: attrs.primitive_optional("sticky")?.unwrap_or(true), + struts: struts.unwrap_or_default(), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)] +pub enum EwwWindowType { + #[default] + Dock, + Dialog, + Toolbar, + Normal, + Utility, +} +impl FromStr for EwwWindowType { + type Err = EnumParseError; + + fn from_str(s: &str) -> Result { + enum_parse! { "window type", s, + "dock" => Self::Dock, + "toolbar" => Self::Toolbar, + "dialog" => Self::Dialog, + "normal" => Self::Normal, + "utility" => Self::Utility, + } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, serde::Serialize)] +pub enum Side { + #[default] + Top, + Left, + Right, + Bottom, +} + +impl std::str::FromStr for Side { + type Err = EnumParseError; + + fn from_str(s: &str) -> Result { + enum_parse! { "side", s, + "l" | "left" => Side::Left, + "r" | "right" => Side::Right, + "t" | "top" => Side::Top, + "b" | "bottom" => Side::Bottom, + } + } +} + +// Surface definition if the backend for X11 is enable +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize)] +pub struct StrutDefinition { + pub side: Side, + pub dist: NumWithUnit, +} + +impl FromAstElementContent for StrutDefinition { + fn get_element_name() -> &'static str { + "struts" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let mut attrs = iter.expect_key_values()?; + Ok(StrutDefinition { side: attrs.primitive_required("side")?, dist: attrs.primitive_required("distance")? }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct WaylandWindowOptions { + pub exclusive: bool, + pub focusable: bool, +} +impl WaylandWindowOptions { + pub fn from_attrs(attrs: &mut Attributes) -> AstResult { + Ok(WaylandWindowOptions { + exclusive: attrs.primitive_optional("exclusive")?.unwrap_or(false), + focusable: attrs.primitive_optional("focusable")?.unwrap_or(false), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct NoBackendWindowOptions; +impl NoBackendWindowOptions { + pub fn from_attrs(attrs: &mut Attributes) -> Result { + Ok(NoBackendWindowOptions) + } +} diff --git a/crates/yuck/src/config/config.rs b/crates/yuck/src/config/config.rs new file mode 100644 index 0000000..b969b4f --- /dev/null +++ b/crates/yuck/src/config/config.rs @@ -0,0 +1,82 @@ +use std::collections::HashMap; + +use simplexpr::SimplExpr; + +use super::{ + script_var_definition::ScriptVarDefinition, var_definition::VarDefinition, widget_definition::WidgetDefinition, + widget_use::WidgetUse, window_definition::WindowDefinition, +}; +use crate::{ + config::script_var_definition::{PollScriptVar, TailScriptVar}, + error::{AstError, AstResult, OptionAstErrorExt}, + parser::{ + ast::{Ast, Span}, + ast_iterator::AstIterator, + from_ast::{FromAst, FromAstElementContent}, + }, + value::{AttrName, VarName}, +}; + +pub enum TopLevel { + VarDefinition(VarDefinition), + ScriptVarDefinition(ScriptVarDefinition), + WidgetDefinition(WidgetDefinition), + WindowDefinition(WindowDefinition), +} + +impl FromAst for TopLevel { + fn from_ast(e: Ast) -> AstResult { + let span = e.span(); + let mut iter = e.try_ast_iter()?; + let (sym_span, element_name) = iter.expect_symbol()?; + Ok(match element_name.as_str() { + x if x == WidgetDefinition::get_element_name() => Self::WidgetDefinition(WidgetDefinition::from_tail(span, iter)?), + x if x == VarDefinition::get_element_name() => Self::VarDefinition(VarDefinition::from_tail(span, iter)?), + x if x == PollScriptVar::get_element_name() => { + Self::ScriptVarDefinition(ScriptVarDefinition::Poll(PollScriptVar::from_tail(span, iter)?)) + } + x if x == TailScriptVar::get_element_name() => { + Self::ScriptVarDefinition(ScriptVarDefinition::Tail(TailScriptVar::from_tail(span, iter)?)) + } + x if x == WindowDefinition::get_element_name() => Self::WindowDefinition(WindowDefinition::from_tail(span, iter)?), + x => return Err(AstError::UnknownToplevel(sym_span, x.to_string())), + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] +pub struct Config { + widget_definitions: HashMap, + window_definitions: HashMap, + var_definitions: HashMap, + script_vars: HashMap, +} + +impl FromAst for Config { + fn from_ast(e: Ast) -> AstResult { + let list = e.as_list()?; + let mut config = Self { + widget_definitions: HashMap::new(), + window_definitions: HashMap::new(), + var_definitions: HashMap::new(), + script_vars: HashMap::new(), + }; + for element in list { + match TopLevel::from_ast(element)? { + TopLevel::VarDefinition(x) => { + config.var_definitions.insert(x.name.clone(), x); + } + TopLevel::ScriptVarDefinition(x) => { + config.script_vars.insert(x.name().clone(), x); + } + TopLevel::WidgetDefinition(x) => { + config.widget_definitions.insert(x.name.clone(), x); + } + TopLevel::WindowDefinition(x) => { + config.window_definitions.insert(x.name.clone(), x); + } + } + } + Ok(config) + } +} diff --git a/crates/yuck/src/config/config_parse_error.rs b/crates/yuck/src/config/config_parse_error.rs new file mode 100644 index 0000000..9a99b01 --- /dev/null +++ b/crates/yuck/src/config/config_parse_error.rs @@ -0,0 +1,2 @@ +#[derive(Debug, thiserror::Error)] +pub enum ConfigParseError {} diff --git a/crates/yuck/src/config/mod.rs b/crates/yuck/src/config/mod.rs new file mode 100644 index 0000000..2b3a558 --- /dev/null +++ b/crates/yuck/src/config/mod.rs @@ -0,0 +1,13 @@ +pub mod attributes; +pub mod backend_window_options; +mod config; +pub mod config_parse_error; +pub mod script_var_definition; +#[cfg(test)] +mod test; +pub mod validate; +pub mod var_definition; +pub mod widget_definition; +pub mod widget_use; +pub mod window_definition; +pub mod window_geometry; diff --git a/crates/yuck/src/config/script_var_definition.rs b/crates/yuck/src/config/script_var_definition.rs new file mode 100644 index 0000000..2d12424 --- /dev/null +++ b/crates/yuck/src/config/script_var_definition.rs @@ -0,0 +1,74 @@ +use std::collections::HashMap; + +use simplexpr::{dynval::DynVal, SimplExpr}; + +use crate::{ + error::{AstError, AstResult}, + parser::{ + ast::{Ast, Span}, + ast_iterator::AstIterator, + from_ast::{FromAst, FromAstElementContent}, + }, + value::{AttrName, VarName}, +}; + +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] +pub enum ScriptVarDefinition { + Poll(PollScriptVar), + Tail(TailScriptVar), +} + +impl ScriptVarDefinition { + pub fn name(&self) -> &VarName { + match self { + ScriptVarDefinition::Poll(x) => &x.name, + ScriptVarDefinition::Tail(x) => &x.name, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] +pub enum VarSource { + // TODO allow for other executors? (python, etc) + Shell(String), + #[serde(skip)] + Function(fn() -> Result>), +} +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] +pub struct PollScriptVar { + pub name: VarName, + pub command: VarSource, + pub interval: std::time::Duration, +} + +impl FromAstElementContent for PollScriptVar { + fn get_element_name() -> &'static str { + "defpollvar" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (_, name) = iter.expect_symbol()?; + let mut attrs = iter.expect_key_values()?; + let interval = attrs.primitive_required::("interval")?.as_duration()?; + // let interval = interval.as_duration()?; + let (_, script) = iter.expect_literal()?; + Ok(Self { name: VarName(name), command: VarSource::Shell(script.to_string()), interval }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] +pub struct TailScriptVar { + pub name: VarName, + pub command: String, +} +impl FromAstElementContent for TailScriptVar { + fn get_element_name() -> &'static str { + "deftailvar" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (_, name) = iter.expect_symbol()?; + let (_, script) = iter.expect_literal()?; + Ok(Self { name: VarName(name), command: script.to_string() }) + } +} diff --git a/crates/yuck/src/config/snapshots/eww_config__config__test__config.snap b/crates/yuck/src/config/snapshots/eww_config__config__test__config.snap new file mode 100644 index 0000000..4316d36 --- /dev/null +++ b/crates/yuck/src/config/snapshots/eww_config__config__test__config.snap @@ -0,0 +1,112 @@ +--- +source: src/config/test.rs +expression: config.unwrap() + +--- +Config( + widget_definitions: { + "bar": WidgetDefinition( + name: "bar", + expected_args: [ + AttrName("arg"), + AttrName("arg2"), + ], + widget: WidgetUse( + name: "text", + attrs: Attributes( + span: Span(99, 104, 0), + attrs: { + AttrName("text"): AttrEntry( + key_span: Span(99, 104, 0), + value: Literal(Span(99, 104, 0), DynVal("bla", None)), + ), + }, + ), + children: [], + span: Span(99, 104, 0), + ), + span: Span(61, 105, 0), + args_span: Span(76, 86, 0), + ), + "foo": WidgetDefinition( + name: "foo", + expected_args: [ + AttrName("arg"), + ], + widget: WidgetUse( + name: "text", + attrs: Attributes( + span: Span(44, 51, 0), + attrs: { + AttrName("text"): AttrEntry( + key_span: Span(44, 51, 0), + value: Literal(Span(44, 51, 0), DynVal("heyho", None)), + ), + }, + ), + children: [], + span: Span(44, 51, 0), + ), + span: Span(11, 52, 0), + args_span: Span(26, 31, 0), + ), + }, + window_definitions: { + "some-window": WindowDefinition( + name: "some-window", + geometry: Some(WindowGeometry( + anchor_point: AnchorPoint( + x: START, + y: START, + ), + offset: Coords( + x: Pixels(0), + y: Pixels(0), + ), + size: Coords( + x: Percent(12), + y: Pixels(20), + ), + )), + stacking: Foreground, + monitor_number: Some(12), + widget: WidgetUse( + name: "foo", + attrs: Attributes( + span: Span(509, 509, 513), + attrs: { + AttrName("arg"): AttrEntry( + key_span: Span(514, 518, 0), + value: Literal(Span(519, 524, 0), DynVal("bla", None)), + ), + }, + ), + children: [], + span: Span(509, 525, 0), + ), + resizable: true, + backend_options: X11WindowOptions( + wm_ignore: false, + sticky: true, + window_type: Dock, + struts: StrutDefinition( + side: Left, + dist: Pixels(30), + ), + ), + ), + }, + var_definitions: { + VarName("some_var"): VarDefinition( + name: VarName("some_var"), + initial_value: DynVal("bla", None), + span: Span(114, 137, 0), + ), + }, + script_vars: { + VarName("stuff"): Tail(TailScriptVar( + name: VarName("stuff"), + command: "tail -f stuff", + )), + }, +) diff --git a/crates/yuck/src/config/test.rs b/crates/yuck/src/config/test.rs new file mode 100644 index 0000000..bd34c77 --- /dev/null +++ b/crates/yuck/src/config/test.rs @@ -0,0 +1,37 @@ +use crate::{ + config::config::Config, + parser::{ + self, + ast::{Ast, Span}, + from_ast::FromAst, + lexer::Lexer, + }, +}; + +#[test] +fn test_config() { + let input = r#" + (defwidget foo [arg] + "heyho") + (defwidget bar [arg arg2] + "bla") + (defvar some_var "bla") + (defpollvar stuff :interval "12s" "date") + (deftailvar stuff "tail -f stuff") + (defwindow some-window + :stacking "fg" + :monitor 12 + :resizable true + :geometry (geometry :width "12%" :height "20px") + :reserve (struts :side "left" :distance "30px") + (foo :arg "bla")) + "#; + + let lexer = Lexer::new(0, input.to_string()); + let p = parser::parser::ToplevelParser::new(); + let (span, parse_result) = p.parse(0, lexer).unwrap(); + let config = Config::from_ast(Ast::List(span, parse_result)); + insta::with_settings!({sort_maps => true}, { + insta::assert_ron_snapshot!(config.unwrap()); + }); +} diff --git a/crates/yuck/src/config/validate.rs b/crates/yuck/src/config/validate.rs new file mode 100644 index 0000000..153656a --- /dev/null +++ b/crates/yuck/src/config/validate.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; + +use simplexpr::SimplExpr; + +use crate::{ + error::AstResult, + parser::{ + ast::{Ast, Span}, + ast_iterator::AstIterator, + from_ast::FromAst, + }, + value::{AttrName, VarName}, +}; + +use super::{widget_definition::WidgetDefinition, widget_use::WidgetUse}; + +#[derive(Debug, thiserror::Error)] +pub enum ValidationError { + #[error("Unknown widget referenced: {1}")] + UnknownWidget(Span, String), + + #[error("Missing attribute `{arg_name}` in use of widget `{widget_name}`")] + MissingAttr { widget_name: String, arg_name: AttrName, arg_list_span: Span, use_span: Span }, +} + +pub fn validate(defs: &HashMap, content: &WidgetUse) -> Result<(), ValidationError> { + if let Some(def) = defs.get(&content.name) { + for expected in def.expected_args.iter() { + if !content.attrs.attrs.contains_key(expected) { + return Err(ValidationError::MissingAttr { + widget_name: def.name.to_string(), + arg_name: expected.clone(), + arg_list_span: def.args_span, + use_span: content.span, + }); + } + } + } else { + return Err(ValidationError::UnknownWidget(content.span, content.name.to_string())); + } + Ok(()) +} diff --git a/crates/yuck/src/config/var_definition.rs b/crates/yuck/src/config/var_definition.rs new file mode 100644 index 0000000..da458d5 --- /dev/null +++ b/crates/yuck/src/config/var_definition.rs @@ -0,0 +1,32 @@ +use std::collections::HashMap; + +use simplexpr::{dynval::DynVal, SimplExpr}; + +use crate::{ + error::AstResult, + parser::{ + ast::{Ast, Span}, + ast_iterator::AstIterator, + from_ast::{FromAst, FromAstElementContent}, + }, + value::{AttrName, VarName}, +}; + +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] +pub struct VarDefinition { + pub name: VarName, + pub initial_value: DynVal, + pub span: Span, +} + +impl FromAstElementContent for VarDefinition { + fn get_element_name() -> &'static str { + "defvar" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (_, name) = iter.expect_symbol()?; + let (_, initial_value) = iter.expect_literal()?; + Ok(Self { name: VarName(name), initial_value, span }) + } +} diff --git a/crates/yuck/src/config/widget_definition.rs b/crates/yuck/src/config/widget_definition.rs new file mode 100644 index 0000000..b7bf2d9 --- /dev/null +++ b/crates/yuck/src/config/widget_definition.rs @@ -0,0 +1,39 @@ +use std::collections::HashMap; + +use simplexpr::SimplExpr; + +use crate::{ + error::AstResult, + parser::{ + ast::{Ast, Span}, + ast_iterator::AstIterator, + from_ast::{FromAst, FromAstElementContent}, + }, + value::{AttrName, VarName}, +}; + +use super::widget_use::WidgetUse; +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] +pub struct WidgetDefinition { + pub name: String, + pub expected_args: Vec, + pub widget: WidgetUse, + pub span: Span, + pub args_span: Span, +} + +impl FromAstElementContent for WidgetDefinition { + fn get_element_name() -> &'static str { + "defwidget" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (_, name) = iter.expect_symbol()?; + let (args_span, expected_args) = iter.expect_array()?; + let expected_args = expected_args.into_iter().map(|x| x.as_symbol().map(AttrName)).collect::>()?; + let widget = iter.expect_any().and_then(WidgetUse::from_ast)?; + // TODO verify that this was the last element in the list + // iter.expect_done()?; + Ok(Self { name, expected_args, widget, span, args_span }) + } +} diff --git a/crates/yuck/src/config/widget_use.rs b/crates/yuck/src/config/widget_use.rs new file mode 100644 index 0000000..78a6cfc --- /dev/null +++ b/crates/yuck/src/config/widget_use.rs @@ -0,0 +1,52 @@ +use std::collections::HashMap; + +use simplexpr::SimplExpr; + +use crate::{ + config::attributes::AttrEntry, + error::AstResult, + parser::{ + ast::{Ast, Span}, + ast_iterator::AstIterator, + from_ast::FromAst, + }, + value::AttrName, +}; + +use super::attributes::Attributes; + +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] +pub struct WidgetUse { + pub name: String, + pub attrs: Attributes, + pub children: Vec, + pub span: Span, +} + +impl FromAst for WidgetUse { + fn from_ast(e: Ast) -> AstResult { + let span = e.span(); + if let Ok(text) = e.as_literal_ref() { + Ok(Self { + name: "text".to_string(), + attrs: Attributes::new( + span.into(), + maplit::hashmap! { + AttrName("text".to_string()) => AttrEntry::new( + span.into(), + Ast::Literal(span.into(), text.clone()) + ) + }, + ), + children: Vec::new(), + span, + }) + } else { + let mut iter = e.try_ast_iter()?; + let (_, name) = iter.expect_symbol()?; + let attrs = iter.expect_key_values()?; + let children = iter.map(WidgetUse::from_ast).collect::>>()?; + Ok(Self { name, attrs, children, span }) + } + } +} diff --git a/crates/yuck/src/config/window_definition.rs b/crates/yuck/src/config/window_definition.rs new file mode 100644 index 0000000..f49f940 --- /dev/null +++ b/crates/yuck/src/config/window_definition.rs @@ -0,0 +1,100 @@ +use std::{collections::HashMap, fmt::Display, str::FromStr}; + +use simplexpr::{dynval::DynVal, SimplExpr}; + +use crate::{ + error::{AstError, AstResult}, + parser::{ + ast::{Ast, Span}, + ast_iterator::AstIterator, + from_ast::{FromAst, FromAstElementContent}, + }, + value::{AttrName, NumWithUnit, VarName}, +}; + +use super::{backend_window_options::BackendWindowOptions, widget_use::WidgetUse, window_geometry::WindowGeometry}; + +#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)] +pub struct WindowDefinition { + pub name: String, + pub geometry: Option, + pub stacking: WindowStacking, + pub monitor_number: Option, + pub widget: WidgetUse, + pub resizable: bool, + pub backend_options: BackendWindowOptions, +} + +impl FromAstElementContent for WindowDefinition { + fn get_element_name() -> &'static str { + "defwindow" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (_, name) = iter.expect_symbol()?; + let mut attrs = iter.expect_key_values()?; + let monitor_number = attrs.primitive_optional("monitor")?; + let resizable = attrs.primitive_optional("resizable")?.unwrap_or(true); + let stacking = attrs.primitive_optional("stacking")?.unwrap_or(WindowStacking::Foreground); + let geometry = attrs.ast_optional("geometry")?; + let backend_options = BackendWindowOptions::from_attrs(&mut attrs)?; + let widget = iter.expect_any()?; + Ok(Self { name, monitor_number, resizable, widget, stacking, geometry, backend_options }) + } +} + +#[derive(Debug, thiserror::Error)] +pub struct EnumParseError { + pub input: String, + pub expected: Vec<&'static str>, +} +impl Display for EnumParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Failed to parse `{}`, must be one of {}", self.input, self.expected.join(", ")) + } +} + +/// Parse a string with a concrete set of options into some data-structure, +/// and return an [EnumParseError] +/// ```rs +/// let input = "up"; +/// enum_parse { "direction", input, +/// "up" => Direction::Up, +/// "down" => Direction::Down, +/// } +/// ``` +#[macro_export] +macro_rules! enum_parse { + ($name:literal, $input:expr, $($($s:literal)|* => $val:expr),* $(,)?) => { + let input = $input.to_lowercase(); + match input.as_str() { + $( $( $s )|* => Ok($val) ),*, + _ => Err(EnumParseError { + input: input, + expected: vec![$($($s),*),*], + }) + } + }; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display, smart_default::SmartDefault, serde::Serialize)] +pub enum WindowStacking { + #[default] + Foreground, + Background, + Bottom, + Overlay, +} + +impl std::str::FromStr for WindowStacking { + type Err = EnumParseError; + + fn from_str(s: &str) -> Result { + enum_parse! { "WindowStacking", s, + "foreground" | "fg" => WindowStacking::Foreground, + "background" | "bg" => WindowStacking::Background, + "bottom" | "bt" => WindowStacking::Bottom, + "overlay" | "ov" => WindowStacking::Overlay, + } + } +} diff --git a/crates/yuck/src/config/window_geometry.rs b/crates/yuck/src/config/window_geometry.rs new file mode 100644 index 0000000..16aac88 --- /dev/null +++ b/crates/yuck/src/config/window_geometry.rs @@ -0,0 +1,150 @@ +use std::collections::HashMap; + +use simplexpr::{dynval::DynVal, SimplExpr}; + +use crate::{enum_parse, error::{AstError, AstResult}, parser::{ + ast::{Ast, Span}, ast_iterator::AstIterator, + from_ast::{FromAst, FromAstElementContent}, + }, value::{AttrName, Coords, VarName}}; + +use super::{widget_use::WidgetUse, window_definition::EnumParseError}; +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, strum::Display)] +pub enum AnchorAlignment { + #[strum(serialize = "start")] + #[default] + START, + #[strum(serialize = "center")] + CENTER, + #[strum(serialize = "end")] + END, +} + +impl AnchorAlignment { + pub fn from_x_alignment(s: &str) -> Result { + enum_parse! { "x-alignment", s, + "l" | "left" => AnchorAlignment::START, + "c" | "center" => AnchorAlignment::CENTER, + "r" | "right" => AnchorAlignment::END, + } + } + + pub fn from_y_alignment(s: &str) -> Result { + enum_parse! { "y-alignment", s, + "t" | "top" => AnchorAlignment::START, + "c" | "center" => AnchorAlignment::CENTER, + "b" | "bottom" => AnchorAlignment::END, + } + } + + pub fn alignment_to_coordinate(&self, size_inner: i32, size_container: i32) -> i32 { + match self { + AnchorAlignment::START => 0, + AnchorAlignment::CENTER => (size_container / 2) - (size_inner / 2), + AnchorAlignment::END => size_container - size_inner, + } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)] +pub struct AnchorPoint { + pub x: AnchorAlignment, + pub y: AnchorAlignment, +} + +impl std::fmt::Display for AnchorPoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use AnchorAlignment::*; + match (self.x, self.y) { + (CENTER, CENTER) => write!(f, "center"), + (x, y) => write!( + f, + "{} {}", + match x { + START => "left", + CENTER => "center", + END => "right", + }, + match y { + START => "top", + CENTER => "center", + END => "bottom", + } + ), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum AnchorPointParseError { + #[error("Could not parse anchor: Must either be \"center\" or be formatted like \"top left\"")] + WrongFormat(String), + #[error(transparent)] + EnumParseError(#[from] EnumParseError), +} + + +impl std::str::FromStr for AnchorPoint { + type Err = AnchorPointParseError; + + fn from_str(s: &str) -> Result { + if s == "center" { + Ok(AnchorPoint { x: AnchorAlignment::CENTER, y: AnchorAlignment::CENTER }) + } else { + let (first, second) = s + .split_once(' ') + .ok_or_else(|| AnchorPointParseError::WrongFormat(s.to_string()))?; + let x_y_result: Result<_, EnumParseError> = try { + AnchorPoint { x: AnchorAlignment::from_x_alignment(first)?, y: AnchorAlignment::from_y_alignment(second)? } + }; + x_y_result.or_else(|_| { + Ok(AnchorPoint { x: AnchorAlignment::from_x_alignment(second)?, y: AnchorAlignment::from_y_alignment(first)? }) + }) + } + } +} + +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Serialize)] +pub struct WindowGeometry { + pub anchor_point: AnchorPoint, + pub offset: Coords, + pub size: Coords, +} + +impl FromAstElementContent for WindowGeometry { + fn get_element_name() -> &'static str { + "geometry" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let mut attrs = iter.expect_key_values()?; + Ok(WindowGeometry { + anchor_point: attrs.primitive_optional("anchor")?.unwrap_or_default(), + size: Coords { + x: attrs.primitive_optional("width")?.unwrap_or_default(), + y: attrs.primitive_optional("height")?.unwrap_or_default(), + }, + offset: Coords { + x: attrs.primitive_optional("x")?.unwrap_or_default(), + y: attrs.primitive_optional("y")?.unwrap_or_default(), + }, + }) + } +} + +impl WindowGeometry { + pub fn override_if_given(&self, anchor_point: Option, offset: Option, size: Option) -> Self { + WindowGeometry { + anchor_point: anchor_point.unwrap_or(self.anchor_point), + offset: offset.unwrap_or(self.offset), + size: size.unwrap_or(self.size), + } + } +} + +impl std::fmt::Display for WindowGeometry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{} ({})", self.offset, self.size, self.anchor_point) + } +} diff --git a/crates/yuck/src/error.rs b/crates/yuck/src/error.rs new file mode 100644 index 0000000..ac99e07 --- /dev/null +++ b/crates/yuck/src/error.rs @@ -0,0 +1,104 @@ +use crate::{ + config::{attributes::AttrError, validate::ValidationError}, + parser::{ + ast::{Ast, AstType, Span}, + lexer, parse_error, + }, +}; +use codespan_reporting::{diagnostic, files}; +use simplexpr::dynval; +use thiserror::Error; + +pub type AstResult = Result; + +#[derive(Debug, Error)] +pub enum AstError { + #[error("Unknown toplevel declaration `{1}`")] + UnknownToplevel(Span, String), + #[error("Expected another element, but got nothing")] + MissingNode(Span), + #[error("Wrong type of expression: Expected {1} but got {2}")] + WrongExprType(Span, AstType, AstType), + #[error("Expected to get a value, but got {1}")] + NotAValue(Span, AstType), + #[error("Expected element {1}, but read {2}")] + MismatchedElementName(Span, String, String), + + #[error(transparent)] + ConversionError(#[from] dynval::ConversionError), + + #[error("{1}")] + Other(Option, Box), + + #[error(transparent)] + AttrError(#[from] AttrError), + + #[error(transparent)] + ValidationError(#[from] ValidationError), + + #[error("Parse error: {source}")] + ParseError { file_id: Option, source: lalrpop_util::ParseError }, +} + +impl AstError { + pub fn get_span(&self) -> Option { + match self { + AstError::UnknownToplevel(span, _) => Some(*span), + AstError::MissingNode(span) => Some(*span), + AstError::WrongExprType(span, ..) => Some(*span), + AstError::NotAValue(span, ..) => Some(*span), + AstError::MismatchedElementName(span, ..) => Some(*span), + AstError::AttrError(err) => Some(err.span()), + AstError::Other(span, ..) => *span, + AstError::ConversionError(err) => err.value.span().map(|x| x.into()), + AstError::ValidationError(error) => None, // TODO none here is stupid + AstError::ParseError { file_id, source } => file_id.and_then(|id| get_parse_error_span(id, source)), + } + } + + pub fn from_parse_error( + file_id: usize, + err: lalrpop_util::ParseError, + ) -> AstError { + AstError::ParseError { file_id: Some(file_id), source: err } + } +} + +fn get_parse_error_span( + file_id: usize, + err: &lalrpop_util::ParseError, +) -> Option { + match err { + lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location, file_id)), + lalrpop_util::ParseError::UnrecognizedEOF { location, expected } => Some(Span(*location, *location, file_id)), + lalrpop_util::ParseError::UnrecognizedToken { token, expected } => Some(Span(token.0, token.2, file_id)), + lalrpop_util::ParseError::ExtraToken { token } => Some(Span(token.0, token.2, file_id)), + lalrpop_util::ParseError::User { error } => match error { + parse_error::ParseError::SimplExpr(span, error) => *span, + parse_error::ParseError::LexicalError(span) => Some(*span), + }, + } +} + +// pub fn spanned(span: Span, err: impl Into) -> AstError { +// use AstError::*; +// match err.into() { +// UnknownToplevel(s, x) => UnknownToplevel(Some(s.unwrap_or(span)), x), +// MissingNode(s) => MissingNode(Some(s.unwrap_or(span))), +// WrongExprType(s, x, y) => WrongExprType(Some(s.unwrap_or(span)), x, y), +// UnknownToplevel(s, x) => UnknownToplevel(Some(s.unwrap_or(span)), x), +// MissingNode(s) => MissingNode(Some(s.unwrap_or(span))), +// NotAValue(s, x) => NotAValue(Some(s.unwrap_or(span)), x), +// MismatchedElementName(s, expected, got) => MismatchedElementName(Some(s.unwrap_or(span)), expected, got), +// Other(s, x) => Other(Some(s.unwrap_or(span)), x), +// x @ ConversionError(_) | x @ AttrError(_) | x @ ValidationError(_) | x @ ParseError { .. } => x, +//} + +pub trait OptionAstErrorExt { + fn or_missing(self, span: Span) -> Result; +} +impl OptionAstErrorExt for Option { + fn or_missing(self, span: Span) -> Result { + self.ok_or(AstError::MissingNode(span)) + } +} diff --git a/crates/yuck/src/format_diagnostic.rs b/crates/yuck/src/format_diagnostic.rs new file mode 100644 index 0000000..a1238c3 --- /dev/null +++ b/crates/yuck/src/format_diagnostic.rs @@ -0,0 +1,137 @@ +use codespan_reporting::{diagnostic, files}; +use simplexpr::dynval; + +use diagnostic::*; + +use crate::error::AstError; + +use super::parser::{ast::Span, parse_error}; + +macro_rules! gen_diagnostic { + ( + $(msg = $msg:expr)? + $(, label = $span:expr $(=> $label:expr)?)? + $(, note = $note:expr)? $(,)? + ) => { + Diagnostic::error() + $(.with_message($msg.to_string()))? + $(.with_labels(vec![ + Label::primary($span.2, $span.0..$span.1) + $(.with_message($label))? + ]))? + $(.with_notes(vec![$note]))? + }; + ($msg:expr $(, $span:expr $(,)?)?) => {{ + Diagnostic::error() + .with_message($msg.to_string()) + $(.with_labels(vec![Label::primary($span.2, $span.0..$span.1)]))? + }}; +} + +pub trait ToDiagnostic { + fn to_diagnostic(&self) -> Diagnostic; +} + +impl ToDiagnostic for AstError { + fn to_diagnostic(&self) -> Diagnostic { + if let AstError::ValidationError(error) = self { + match error { + crate::config::validate::ValidationError::UnknownWidget(span, name) => gen_diagnostic! { + msg = format!("No widget named {} exists", name), + label = span => "Used here", + }, + crate::config::validate::ValidationError::MissingAttr { widget_name, arg_name, arg_list_span, use_span } => { + let diag = gen_diagnostic! { + msg = format!("{}", error), + }; + diag.with_labels(vec![ + Label::secondary(use_span.2, use_span.0..use_span.1).with_message("Argument missing here"), + Label::secondary(arg_list_span.2, arg_list_span.0..arg_list_span.1).with_message("but is required here"), + ]) + } + } + } else if let Some(span) = self.get_span() { + match self { + AstError::UnknownToplevel(_, name) => gen_diagnostic!(format!("{}", self), span), + AstError::MissingNode(_) => gen_diagnostic! { + msg = "Expected another element", + label = span => "Expected another element here", + }, + + AstError::WrongExprType(_, expected, actual) => gen_diagnostic! { + msg = "Wrong type of expression", + label = span => format!("Expected a `{}` here", expected), + note = format!("Expected: {}\nGot: {}", expected, actual), + }, + AstError::NotAValue(_, actual) => gen_diagnostic! { + msg = format!("Expected value, but got `{}`", actual), + label = span => "Expected some value here", + note = format!("Got: {}", actual), + }, + + AstError::ParseError { file_id, source } => lalrpop_error_to_diagnostic(source, span, |error| match error { + parse_error::ParseError::SimplExpr(_, error) => simplexpr_error_to_diagnostic(error, span), + parse_error::ParseError::LexicalError(_) => lexical_error_to_diagnostic(span), + }), + AstError::MismatchedElementName(_, expected, got) => gen_diagnostic! { + msg = format!("Expected element `{}`, but found `{}`", expected, got), + label = span => format!("Expected `{}` here", expected), + note = format!("Expected: {}\nGot: {}", expected, got), + }, + AstError::ConversionError(err) => conversion_error_to_diagnostic(err, span), + AstError::Other(_, source) => gen_diagnostic!(source, span), + AstError::AttrError(source) => gen_diagnostic!(source, span), + AstError::ValidationError(_) => todo!(), + } + } else { + Diagnostic::error().with_message(format!("{}", self)) + } + } +} + +fn lalrpop_error_to_diagnostic( + error: &lalrpop_util::ParseError, + span: Span, + handle_user_error: impl FnOnce(&E) -> Diagnostic, +) -> Diagnostic { + use lalrpop_util::ParseError::*; + match error { + InvalidToken { location } => gen_diagnostic! { msg = "Invalid token", label = span }, + UnrecognizedEOF { location, expected } => gen_diagnostic! { + msg = "Input ended unexpectedly. Check if you have any unclosed delimiters", + label = span + }, + UnrecognizedToken { token, expected } => gen_diagnostic! { + msg = format!("Unexpected token `{}` encountered", token.1), + label = span => "Token unexpected", + }, + ExtraToken { token } => gen_diagnostic!(format!("Extra token encountered: `{}`", token.1)), + User { error } => handle_user_error(error), + } +} + +fn simplexpr_error_to_diagnostic(error: &simplexpr::error::Error, span: Span) -> Diagnostic { + use simplexpr::error::Error::*; + match error { + ParseError { source, .. } => lalrpop_error_to_diagnostic(source, span, move |error| lexical_error_to_diagnostic(span)), + ConversionError(error) => conversion_error_to_diagnostic(error, span), + Eval(error) => gen_diagnostic!(error, span), + Other(error) => gen_diagnostic!(error, span), + Spanned(_, error) => gen_diagnostic!(error, span), + } +} + +fn conversion_error_to_diagnostic(error: &dynval::ConversionError, span: Span) -> Diagnostic { + let diag = gen_diagnostic! { + msg = format!("{}", error), + label = span => format!("{} is not of type `{}`", error.value, error.target_type), + }; + diag.with_notes(error.source.as_ref().map(|x| vec![format!("{}", x)]).unwrap_or_default()) +} + +fn lexical_error_to_diagnostic(span: Span) -> Diagnostic { + gen_diagnostic! { + msg = "Invalid token", + label = span => "Invalid token" + } +} diff --git a/crates/yuck/src/lib.rs b/crates/yuck/src/lib.rs new file mode 100644 index 0000000..91cace7 --- /dev/null +++ b/crates/yuck/src/lib.rs @@ -0,0 +1,10 @@ +#![allow(unused_imports)] +#![allow(unused)] +#![feature(try_blocks)] + +pub mod config; +pub mod error; +pub mod format_diagnostic; +pub mod parser; +mod util; +pub mod value; diff --git a/crates/yuck/src/parser/ast.rs b/crates/yuck/src/parser/ast.rs new file mode 100644 index 0000000..24a6abc --- /dev/null +++ b/crates/yuck/src/parser/ast.rs @@ -0,0 +1,187 @@ +use itertools::Itertools; +use simplexpr::{ast::SimplExpr, dynval::DynVal}; +use std::collections::HashMap; + +use std::fmt::Display; + +use super::{ast_iterator::AstIterator, from_ast::FromAst}; +use crate::{ + config::attributes::{AttrEntry, Attributes}, + error::{AstError, AstResult, OptionAstErrorExt}, + value::AttrName, +}; + +#[derive(Eq, PartialEq, Clone, Copy, serde::Serialize)] +pub struct Span(pub usize, pub usize, pub usize); + +impl Span { + /// Get the span that includes this and the other span completely. + /// Will panic if the spans are from different file_ids. + pub fn to(mut self, other: Span) -> Self { + assert!(other.2 == self.2); + self.1 = other.1; + self + } + + pub fn ending_at(mut self, end: usize) -> Self { + self.1 = end; + self + } + + pub fn with_length(mut self, end: usize) -> Self { + self.1 = self.0; + self + } +} + +impl Into for Span { + fn into(self) -> simplexpr::Span { + simplexpr::Span(self.0, self.1, self.2) + } +} +impl From for Span { + fn from(x: simplexpr::Span) -> Span { + Span(x.0, x.1, x.2) + } +} + +impl std::fmt::Display for Span { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}..{}", self.0, self.1) + } +} + +impl std::fmt::Debug for Span { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}..{}", self.0, self.1) + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum AstType { + List, + Array, + Keyword, + Symbol, + Literal, + SimplExpr, + Comment, + /// A value that could be used as a [SimplExpr] + IntoPrimitive, +} + +impl Display for AstType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(PartialEq, Eq, Clone, serde::Serialize)] +pub enum Ast { + List(Span, Vec), + Array(Span, Vec), + Keyword(Span, String), + Symbol(Span, String), + Literal(Span, DynVal), + SimplExpr(Span, SimplExpr), + Comment(Span), +} + +macro_rules! as_func { + ($exprtype:expr, $name:ident $nameref:ident < $t:ty > = $p:pat => $value:expr) => { + pub fn $name(self) -> Result<$t, AstError> { + match self { + $p => Ok($value), + x => Err(AstError::WrongExprType(x.span(), $exprtype, x.expr_type())), + } + } + + pub fn $nameref(&self) -> Result<&$t, AstError> { + match self { + $p => Ok($value), + x => Err(AstError::WrongExprType(x.span(), $exprtype, x.expr_type())), + } + } + }; +} + +impl Ast { + as_func!(AstType::Literal, as_literal as_literal_ref = Ast::Literal(_, x) => x); + + as_func!(AstType::Symbol, as_symbol as_symbol_ref = Ast::Symbol(_, x) => x); + + as_func!(AstType::Keyword, as_keyword as_keyword_ref = Ast::Keyword(_, x) => x); + + as_func!(AstType::List, as_list as_list_ref> = Ast::List(_, x) => x); + + pub fn expr_type(&self) -> AstType { + match self { + Ast::List(..) => AstType::List, + Ast::Array(..) => AstType::Array, + Ast::Keyword(..) => AstType::Keyword, + Ast::Symbol(..) => AstType::Symbol, + Ast::Literal(..) => AstType::Literal, + Ast::SimplExpr(..) => AstType::SimplExpr, + Ast::Comment(_) => AstType::Comment, + } + } + + pub fn span(&self) -> Span { + match self { + Ast::List(span, _) => *span, + Ast::Array(span, _) => *span, + Ast::Keyword(span, _) => *span, + Ast::Symbol(span, _) => *span, + Ast::Literal(span, _) => *span, + Ast::SimplExpr(span, _) => *span, + Ast::Comment(span) => *span, + } + } + + pub fn as_simplexpr(self) -> AstResult { + match self { + // do I do this? + // Ast::Array(_, _) => todo!(), + // Ast::Symbol(_, _) => todo!(), + Ast::Literal(span, x) => Ok(SimplExpr::Literal(span.into(), x)), + Ast::SimplExpr(span, x) => Ok(x), + _ => Err(AstError::WrongExprType(self.span(), AstType::IntoPrimitive, self.expr_type())), + } + } + + pub fn try_ast_iter(self) -> AstResult>> { + let span = self.span(); + let list = self.as_list()?; + Ok(AstIterator::new(span, list.into_iter())) + } +} + +impl std::fmt::Display for Ast { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use Ast::*; + match self { + List(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")), + Array(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")), + Keyword(_, x) => write!(f, ":{}", x), + Symbol(_, x) => write!(f, "{}", x), + Literal(_, x) => write!(f, "\"{}\"", x), + SimplExpr(_, x) => write!(f, "{{{}}}", x), + Comment(_) => write!(f, ""), + } + } +} +impl std::fmt::Debug for Ast { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use Ast::*; + write!(f, "{}", self) + // match self { + // List(span, x) => f.debug_tuple(&format!("List<{}>", span)).field(x).finish(), + // Array(span, x) => f.debug_tuple(&format!("Array<{}>", span)).field(x).finish(), + // Keyword(span, x) => write!(f, "Number<{}>({})", span, x), + // Symbol(span, x) => write!(f, "Symbol<{}>({})", span, x), + // Value(span, x) => write!(f, "Value<{}>({})", span, x), + // SimplExpr(span, x) => write!(f, "SimplExpr<{}>({})", span, x), + // Comment(span) => write!(f, "Comment<{}>", span), + //} + } +} diff --git a/crates/yuck/src/parser/ast_iterator.rs b/crates/yuck/src/parser/ast_iterator.rs new file mode 100644 index 0000000..6f841d0 --- /dev/null +++ b/crates/yuck/src/parser/ast_iterator.rs @@ -0,0 +1,100 @@ +use itertools::Itertools; +use simplexpr::{ast::SimplExpr, dynval::DynVal}; +use std::collections::HashMap; + +use std::fmt::Display; + +use super::{ + ast::{Ast, AstType, Span}, + from_ast::FromAst, +}; +use crate::{ + config::attributes::{AttrEntry, Attributes}, + error::{AstError, AstResult, OptionAstErrorExt}, + value::AttrName, +}; + +pub struct AstIterator> { + remaining_span: Span, + iter: itertools::PutBack, +} + +macro_rules! return_or_put_back { + ($name:ident, $expr_type:expr, $t:ty = $p:pat => $ret:expr) => { + pub fn $name(&mut self) -> AstResult<$t> { + let expr_type = $expr_type; + match self.expect_any()? { + $p => { + let (span, value) = $ret; + self.remaining_span.1 = span.1; + Ok((span, value)) + } + other => { + let span = other.span(); + let actual_type = other.expr_type(); + self.iter.put_back(other); + Err(AstError::WrongExprType(span, expr_type, actual_type)) + } + } + } + }; +} + +impl> AstIterator { + return_or_put_back!(expect_symbol, AstType::Symbol, (Span, String) = Ast::Symbol(span, x) => (span, x)); + + return_or_put_back!(expect_literal, AstType::Literal, (Span, DynVal) = Ast::Literal(span, x) => (span, x)); + + return_or_put_back!(expect_list, AstType::List, (Span, Vec) = Ast::List(span, x) => (span, x)); + + return_or_put_back!(expect_array, AstType::Array, (Span, Vec) = Ast::Array(span, x) => (span, x)); + + pub fn new(span: Span, iter: I) -> Self { + AstIterator { remaining_span: span, iter: itertools::put_back(iter) } + } + + pub fn expect_any(&mut self) -> AstResult { + self.iter.next().or_missing(self.remaining_span.with_length(0)).and_then(T::from_ast) + } + + pub fn expect_key_values(&mut self) -> AstResult { + parse_key_values(self) + } +} + +impl> Iterator for AstIterator { + type Item = Ast; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +/// Parse consecutive `:keyword value` pairs from an expression iterator into an [Attributes]. +fn parse_key_values(iter: &mut AstIterator>) -> AstResult { + let mut data = HashMap::new(); + let mut attrs_span = Span(iter.remaining_span.0, iter.remaining_span.0, iter.remaining_span.1); + loop { + match iter.next() { + Some(Ast::Keyword(key_span, kw)) => match iter.next() { + Some(value) => { + attrs_span.1 = iter.remaining_span.0; + let attr_value = AttrEntry { key_span, value }; + data.insert(AttrName(kw), attr_value); + } + None => { + iter.iter.put_back(Ast::Keyword(key_span, kw)); + attrs_span.1 = iter.remaining_span.0; + return Ok(Attributes::new(attrs_span, data)); + } + }, + next => { + if let Some(expr) = next { + iter.iter.put_back(expr); + } + attrs_span.1 = iter.remaining_span.0; + return Ok(Attributes::new(attrs_span, data)); + } + } + } +} diff --git a/crates/yuck/src/parser/from_ast.rs b/crates/yuck/src/parser/from_ast.rs new file mode 100644 index 0000000..bca20b4 --- /dev/null +++ b/crates/yuck/src/parser/from_ast.rs @@ -0,0 +1,58 @@ +use super::{ + ast::{Ast, AstType, Span}, + ast_iterator::AstIterator, +}; +use crate::{error::*, parser, util, value::AttrName}; +use itertools::Itertools; +use simplexpr::{ast::SimplExpr, dynval::DynVal}; +use std::{ + collections::{HashMap, LinkedList}, + iter::FromIterator, + str::FromStr, +}; + +pub trait FromAst: Sized { + fn from_ast(e: Ast) -> AstResult; +} + +impl FromAst for Ast { + fn from_ast(e: Ast) -> AstResult { + Ok(e) + } +} + +impl FromAst for String { + fn from_ast(e: Ast) -> AstResult { + Ok(e.as_literal()?.as_string().unwrap()) + } +} + +/// A trait that allows creating a type from the tail of a list-node. +/// I.e. to parse (foo [a b] (c d)), [from_tail] would just get [a b] (c d). +pub trait FromAstElementContent: Sized { + fn get_element_name() -> &'static str; + fn from_tail>(span: Span, iter: AstIterator) -> AstResult; +} + +impl FromAst for T { + fn from_ast(e: Ast) -> AstResult { + let span = e.span(); + let mut iter = e.try_ast_iter()?; + let (_, element_name) = iter.expect_symbol()?; + if Self::get_element_name() != element_name { + return Err(AstError::MismatchedElementName(span, Self::get_element_name().to_string(), element_name)); + } + Self::from_tail(span, iter) + } +} + +impl FromAst for SimplExpr { + fn from_ast(e: Ast) -> AstResult { + match e { + Ast::Symbol(span, x) => Ok(SimplExpr::VarRef(span.into(), x)), + Ast::Literal(span, x) => Ok(SimplExpr::Literal(span.into(), x)), + Ast::SimplExpr(span, x) => Ok(x), + _ => Err(AstError::NotAValue(e.span(), e.expr_type())), + } + } +} diff --git a/crates/yuck/src/parser/lexer.rs b/crates/yuck/src/parser/lexer.rs new file mode 100644 index 0000000..eb74f14 --- /dev/null +++ b/crates/yuck/src/parser/lexer.rs @@ -0,0 +1,151 @@ +use regex::{Regex, RegexSet}; + +use super::{ast::Span, parse_error}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Token { + LPren, + RPren, + LBrack, + RBrack, + True, + False, + StrLit(String), + NumLit(String), + Symbol(String), + Keyword(String), + SimplExpr(String), + Comment, + Skip, +} + +impl std::fmt::Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Token::LPren => write!(f, "'('"), + Token::RPren => write!(f, "')'"), + Token::LBrack => write!(f, "'['"), + Token::RBrack => write!(f, "']'"), + Token::True => write!(f, "true"), + Token::False => write!(f, "false"), + Token::StrLit(x) => write!(f, "\"{}\"", x), + Token::NumLit(x) => write!(f, "{}", x), + Token::Symbol(x) => write!(f, "{}", x), + Token::Keyword(x) => write!(f, "{}", x), + Token::SimplExpr(x) => write!(f, "{{{}}}", x), + Token::Comment => write!(f, ""), + Token::Skip => write!(f, ""), + } + } +} + +macro_rules! regex_rules { + ($( + $regex:literal => $token:expr),* + ) => { + lazy_static::lazy_static! { + static ref LEXER_REGEX_SET: RegexSet = RegexSet::new(&[ + $(format!("^{}", $regex)),* + ]).unwrap(); + static ref LEXER_REGEXES: Vec = vec![ + $(Regex::new(&format!("^{}", $regex)).unwrap()),* + ]; + static ref LEXER_FNS: Vec Token + Sync>> = vec![ + $(Box::new($token)),* + ]; + } + } +} + +regex_rules! { + r"\(" => |_| Token::LPren, + r"\)" => |_| Token::RPren, + r"\[" => |_| Token::LBrack, + r"\]" => |_| Token::RBrack, + r"true" => |_| Token::True, + r"false" => |_| Token::False, + r#""(?:[^"\\]|\\.)*""# => |x| Token::StrLit(x), + r#"[+-]?(?:[0-9]+[.])?[0-9]+"# => |x| Token::NumLit(x), + r#":[^\s\)\]}]+"# => |x| Token::Keyword(x), + r#"[a-zA-Z_!\?<>/\.\*-\+][^\s{}\(\)\[\](){}]*"# => |x| Token::Symbol(x), + r#";.*"# => |_| Token::Comment, + r"[ \t\n\f]+" => |_| Token::Skip +} + +pub struct Lexer { + source: String, + file_id: usize, + failed: bool, + pos: usize, +} + +impl Lexer { + pub fn new(file_id: usize, source: String) -> Self { + Lexer { source, file_id, failed: false, pos: 0 } + } +} + +impl Iterator for Lexer { + type Item = Result<(usize, Token, usize), parse_error::ParseError>; + + fn next(&mut self) -> Option { + loop { + if self.failed || self.pos >= self.source.len() { + return None; + } + let string = &self.source[self.pos..]; + + if string.starts_with('{') { + self.pos += 1; + let expr_start = self.pos; + let mut in_string = false; + loop { + if self.pos >= self.source.len() { + return None; + } + let string = &self.source[self.pos..]; + + if string.starts_with('}') && !in_string { + let tok_str = &self.source[expr_start..self.pos]; + self.pos += 1; + return Some(Ok((expr_start, Token::SimplExpr(tok_str.to_string()), self.pos - 1))); + } else if string.starts_with('"') { + self.pos += 1; + in_string = !in_string; + } else if string.starts_with("\\\"") { + self.pos += 2; + } else { + self.pos += 1; + } + } + } else { + let match_set = LEXER_REGEX_SET.matches(string); + let matched_token = match_set + .into_iter() + .map(|i: usize| { + let m = LEXER_REGEXES[i].find(string).unwrap(); + (m.end(), i) + }) + .min_by_key(|(_, x)| *x); + + let (len, i) = match matched_token { + Some(x) => x, + None => { + self.failed = true; + return Some(Err(parse_error::ParseError::LexicalError(Span(self.pos, self.pos, self.file_id)))); + } + }; + + let tok_str = &self.source[self.pos..self.pos + len]; + let old_pos = self.pos; + self.pos += len; + match LEXER_FNS[i](tok_str.to_string()) { + Token::Skip | Token::Comment => {} + token => { + return Some(Ok((old_pos, token, self.pos))); + } + } + } + } + } +} diff --git a/crates/yuck/src/parser/mod.rs b/crates/yuck/src/parser/mod.rs new file mode 100644 index 0000000..c4654b2 --- /dev/null +++ b/crates/yuck/src/parser/mod.rs @@ -0,0 +1,63 @@ +use lalrpop_util::lalrpop_mod; + +use super::error::{AstError, AstResult}; +use ast::Ast; + +use std::{fmt::Display, ops::Deref}; + +use itertools::Itertools; + +pub mod ast; +pub mod ast_iterator; +pub mod from_ast; +pub(crate) mod lexer; +pub(crate) mod parse_error; + +lalrpop_mod!( + #[allow(clippy::all)] + pub parser, + "/parser/parser.rs" +); + +pub fn parse_string(file_id: usize, s: &str) -> AstResult { + let lexer = lexer::Lexer::new(file_id, s.to_string()); + let parser = parser::AstParser::new(); + parser.parse(file_id, lexer).map_err(|e| AstError::from_parse_error(file_id, e)) +} + +macro_rules! test_parser { + ($($text:literal),*) => {{ + let p = parser::AstParser::new(); + use lexer::Lexer; + + ::insta::with_settings!({sort_maps => true}, { + $( + ::insta::assert_debug_snapshot!(p.parse(0, Lexer::new(0, $text.to_string()))); + )* + }); + }} +} + +#[test] +fn test() { + test_parser!( + "1", + "(12)", + "1.2", + "-1.2", + "(1 2)", + "(1 :foo 1)", + "(:foo 1)", + "(:foo->: 1)", + "(foo 1)", + "(lol😄 1)", + r#"(test "hi")"#, + r#"(test "h\"i")"#, + r#"(test " hi ")"#, + "(+ (1 2 (* 2 5)))", + r#"foo ; test"#, + r#"(f arg ; test + arg2)"#, + "\"h\\\"i\"" + ); +} diff --git a/crates/yuck/src/parser/parse_error.rs b/crates/yuck/src/parser/parse_error.rs new file mode 100644 index 0000000..9c2fe44 --- /dev/null +++ b/crates/yuck/src/parser/parse_error.rs @@ -0,0 +1,10 @@ +use super::ast::Span; + +#[derive(Debug, thiserror::Error)] +pub enum ParseError { + #[error("{1}")] + SimplExpr(Option, simplexpr::error::Error), + + #[error("Unknown token")] + LexicalError(Span), +} diff --git a/crates/yuck/src/parser/parser.lalrpop b/crates/yuck/src/parser/parser.lalrpop new file mode 100644 index 0000000..110c749 --- /dev/null +++ b/crates/yuck/src/parser/parser.lalrpop @@ -0,0 +1,75 @@ +use std::str::FromStr; +use crate::parser::{lexer::{Token}, ast::{Ast, Span}, parse_error}; +use simplexpr::ast::SimplExpr; +use simplexpr; +use lalrpop_util::ParseError; + +grammar(file_id: usize); + +extern { + type Location = usize; + type Error = parse_error::ParseError; + + enum Token { + "(" => Token::LPren, + ")" => Token::RPren, + "[" => Token::LBrack, + "]" => Token::RBrack, + "true" => Token::True, + "false" => Token::False, + "string" => Token::StrLit(), + "number" => Token::NumLit(), + "symbol" => Token::Symbol(), + "keyword" => Token::Keyword(), + "simplexpr" => Token::SimplExpr(), + "comment" => Token::Comment, + } +} + +pub Toplevel: (Span, Vec) = { + )*> => (Span(l, r, file_id), elems) +} + +pub Ast: Ast = { + "(" )*> ")" => Ast::List(Span(l, r, file_id), elems), + "[" )*> "]" => Ast::Array(Span(l, r, file_id), elems), + => Ast::SimplExpr(Span(l, r, file_id), expr), + => x, + => x, + => Ast::Literal(Span(l, r, file_id), x.into()), + "comment" => Ast::Comment(Span(l, r, file_id)), +}; + +Keyword: Ast = => Ast::Keyword(Span(l, r, file_id), x[1..].to_string()); +Symbol: Ast = => Ast::Symbol(Span(l, r, file_id), x.to_string()); + +Literal: String = { + => <>, + => <>, + => <>, +}; + +StrLit: String = { + => { + x[1..x.len() - 1].to_owned() + }, +}; + +SimplExpr: SimplExpr = { + =>? { + let expr = x[1..x.len() - 1].to_string(); + simplexpr::parse_string(file_id, &expr).map_err(|e| { + let span = e.get_span().map(|simplexpr::Span(simpl_l, simpl_r, file_id)| Span(1 + l + simpl_l, 1 + l + simpl_r, file_id)); + ParseError::User { error: parse_error::ParseError::SimplExpr(span, e) }}) + } +} + + +Num: String = <"number"> => <>.to_string(); +Bool: String = { + "true" => "true".to_string(), + "false" => "false".to_string(), +} + + +// vim:shiftwidth=4 diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__element__test__test.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__element__test__test.snap new file mode 100644 index 0000000..f38b397 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__element__test__test.snap @@ -0,0 +1,17 @@ +--- +source: src/parser/element.rs +expression: "Element::::from_ast(parser.parse(0, lexer).unwrap()).unwrap()" + +--- +Element { + name: "box", + attrs: { + "baz": "hi", + "bar": "12", + }, + children: [ + foo, + (bar), + ], + span: 0..33, +} diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-10.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-10.snap new file mode 100644 index 0000000..86604ce --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-10.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"(lol😄 1)\".to_string()))" + +--- +Ok( + (lol😄 "1"), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-11.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-11.snap new file mode 100644 index 0000000..ac7da83 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-11.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, r#\"(test \"hi\")\"#.to_string()))" + +--- +Ok( + (test "hi"), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-12.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-12.snap new file mode 100644 index 0000000..1906a01 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-12.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, r#\"(test \"h\\\"i\")\"#.to_string()))" + +--- +Ok( + (test "h\"i"), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-13.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-13.snap new file mode 100644 index 0000000..53ceea1 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-13.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, r#\"(test \" hi \")\"#.to_string()))" + +--- +Ok( + (test " hi "), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-14.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-14.snap new file mode 100644 index 0000000..de774e7 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-14.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"(+ (1 2 (* 2 5)))\".to_string()))" + +--- +Ok( + (+ ("1" "2" (* "2" "5"))), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-15.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-15.snap new file mode 100644 index 0000000..929bfab --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-15.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, r#\"foo ; test\"#.to_string()))" + +--- +Ok( + foo, +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-16.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-16.snap new file mode 100644 index 0000000..bd07751 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-16.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, r#\"(f arg ; test\n arg2)\"#.to_string()))" + +--- +Ok( + (f arg arg2), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-17.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-17.snap new file mode 100644 index 0000000..23356d7 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-17.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"\\\"h\\\\\\\"i\\\"\".to_string()))" + +--- +Ok( + "h\"i", +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-2.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-2.snap new file mode 100644 index 0000000..43fb4a4 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-2.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"(12)\".to_string()))" + +--- +Ok( + ("12"), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-3.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-3.snap new file mode 100644 index 0000000..fb2160d --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-3.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"1.2\".to_string()))" + +--- +Ok( + "1.2", +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-4.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-4.snap new file mode 100644 index 0000000..464f39b --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-4.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"-1.2\".to_string()))" + +--- +Ok( + "-1.2", +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-5.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-5.snap new file mode 100644 index 0000000..d1331e7 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-5.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"(1 2)\".to_string()))" + +--- +Ok( + ("1" "2"), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-6.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-6.snap new file mode 100644 index 0000000..31b00ed --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-6.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"(1 :foo 1)\".to_string()))" + +--- +Ok( + ("1" :foo "1"), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-7.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-7.snap new file mode 100644 index 0000000..e0454d1 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-7.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"(:foo 1)\".to_string()))" + +--- +Ok( + (:foo "1"), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-8.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-8.snap new file mode 100644 index 0000000..9025a0f --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-8.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"(:foo->: 1)\".to_string()))" + +--- +Ok( + (:foo->: "1"), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test-9.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test-9.snap new file mode 100644 index 0000000..503bb62 --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test-9.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"(foo 1)\".to_string()))" + +--- +Ok( + (foo "1"), +) diff --git a/crates/yuck/src/parser/snapshots/eww_config__parser__test.snap b/crates/yuck/src/parser/snapshots/eww_config__parser__test.snap new file mode 100644 index 0000000..befaabc --- /dev/null +++ b/crates/yuck/src/parser/snapshots/eww_config__parser__test.snap @@ -0,0 +1,8 @@ +--- +source: src/parser/mod.rs +expression: "p.parse(0, Lexer::new(0, \"1\".to_string()))" + +--- +Ok( + "1", +) diff --git a/crates/yuck/src/util.rs b/crates/yuck/src/util.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/yuck/src/value/coords.rs b/crates/yuck/src/value/coords.rs new file mode 100644 index 0000000..11728d6 --- /dev/null +++ b/crates/yuck/src/value/coords.rs @@ -0,0 +1,112 @@ +use derive_more::*; +use serde::{Deserialize, Serialize}; +use smart_default::SmartDefault; +use std::{fmt, str::FromStr}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Failed to parse \"{0}\" as a length value")] + NumParseFailed(String), + #[error("Inalid unit \"{0}\", must be either % or px")] + InvalidUnit(String), + #[error("Invalid format. Coordinates must be formated like 200x100")] + MalformedCoords, +} + +#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, DebugCustom, SmartDefault)] +pub enum NumWithUnit { + #[display(fmt = "{}%", .0)] + #[debug(fmt = "{}%", .0)] + Percent(i32), + #[display(fmt = "{}px", .0)] + #[debug(fmt = "{}px", .0)] + #[default] + Pixels(i32), +} + +impl NumWithUnit { + pub fn relative_to(&self, max: i32) -> i32 { + match *self { + NumWithUnit::Percent(n) => ((max as f64 / 100.0) * n as f64) as i32, + NumWithUnit::Pixels(n) => n, + } + } +} + +impl FromStr for NumWithUnit { + type Err = Error; + + fn from_str(s: &str) -> Result { + lazy_static::lazy_static! { + static ref PATTERN: regex::Regex = regex::Regex::new("^(-?\\d+)(.*)$").unwrap(); + }; + + let captures = PATTERN.captures(s).ok_or_else(|| Error::NumParseFailed(s.to_string()))?; + let value = captures.get(1).unwrap().as_str().parse::().map_err(|_| Error::NumParseFailed(s.to_string()))?; + match captures.get(2).unwrap().as_str() { + "px" | "" => Ok(NumWithUnit::Pixels(value)), + "%" => Ok(NumWithUnit::Percent(value)), + unit => Err(Error::InvalidUnit(unit.to_string())), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, Default)] +#[display(fmt = "{}*{}", x, y)] +pub struct Coords { + pub x: NumWithUnit, + pub y: NumWithUnit, +} + +impl FromStr for Coords { + type Err = Error; + + fn from_str(s: &str) -> Result { + let (x, y) = s + .split_once(|x: char| x.to_ascii_lowercase() == 'x' || x.to_ascii_lowercase() == '*') + .ok_or(Error::MalformedCoords)?; + Coords::from_strs(x, y) + } +} + +impl fmt::Debug for Coords { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CoordsWithUnits({}, {})", self.x, self.y) + } +} + +impl Coords { + pub fn from_pixels(x: i32, y: i32) -> Self { + Coords { x: NumWithUnit::Pixels(x), y: NumWithUnit::Pixels(y) } + } + + /// parse a string for x and a string for y into a [`Coords`] object. + pub fn from_strs(x: &str, y: &str) -> Result { + Ok(Coords { x: x.parse()?, y: y.parse()? }) + } + + /// resolve the possibly relative coordinates relative to a given containers size + pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) { + (self.x.relative_to(width), self.y.relative_to(height)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_num_with_unit() { + assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55").unwrap()); + assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55px").unwrap()); + assert_eq!(NumWithUnit::Percent(55), NumWithUnit::from_str("55%").unwrap()); + assert!(NumWithUnit::from_str("55pp").is_err()); + } + + #[test] + fn test_parse_coords() { + assert_eq!(Coords { x: NumWithUnit::Pixels(50), y: NumWithUnit::Pixels(60) }, Coords::from_str("50x60").unwrap()); + assert!(Coords::from_str("5060").is_err()); + } +} diff --git a/crates/yuck/src/value/mod.rs b/crates/yuck/src/value/mod.rs new file mode 100644 index 0000000..b3e80a0 --- /dev/null +++ b/crates/yuck/src/value/mod.rs @@ -0,0 +1,41 @@ +use derive_more::*; +use serde::{Deserialize, Serialize}; + +pub mod coords; +pub use coords::*; + +/// The name of a variable +#[repr(transparent)] +#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)] +#[debug(fmt = "VarName({})", .0)] +pub struct VarName(pub String); + +impl std::borrow::Borrow for VarName { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl From<&str> for VarName { + fn from(s: &str) -> Self { + VarName(s.to_owned()) + } +} + +/// The name of an attribute +#[repr(transparent)] +#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)] +#[debug(fmt="AttrName({})", .0)] +pub struct AttrName(pub String); + +impl std::borrow::Borrow for AttrName { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl From<&str> for AttrName { + fn from(s: &str) -> Self { + AttrName(s.to_owned()) + } +}