From 9a8bbf4114acf5039739d97e7c1b518e647cdb54 Mon Sep 17 00:00:00 2001 From: ElKowar <5300871+elkowar@users.noreply.github.com> Date: Tue, 2 Mar 2021 10:27:52 +0100 Subject: [PATCH] Expression language (#124) * Add AST * add make-shift testing parser, and make stuff ocmpile * add proper expression parser * make string format use ' * Add empty doc page for expressions * add tests * Clean up file structure and add unary operators * Write documentation --- Cargo.lock | 309 ++++++++++++++--------- Cargo.toml | 3 +- docs/content/main/expression_language.md | 33 +++ src/config/eww_config.rs | 1 + src/eww_state.rs | 2 +- src/main.rs | 3 + src/value/{ => attr_value}/attr_value.rs | 63 +++-- src/value/attr_value/attr_value_expr.rs | 179 +++++++++++++ src/value/attr_value/mod.rs | 5 + src/value/attr_value/parser.rs | 209 +++++++++++++++ src/value/mod.rs | 2 +- src/value/primitive.rs | 19 +- src/widgets/widget_node.rs | 1 - 13 files changed, 678 insertions(+), 151 deletions(-) create mode 100644 docs/content/main/expression_language.md rename src/value/{ => attr_value}/attr_value.rs (71%) create mode 100644 src/value/attr_value/attr_value_expr.rs create mode 100644 src/value/attr_value/mod.rs create mode 100644 src/value/attr_value/parser.rs diff --git a/Cargo.lock b/Cargo.lock index 7dde666..76b6f6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,9 +26,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee67c11feeac938fae061b232e38e0b6d94f97a9df10e6271319325ac4c56a86" +checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "async-stream" @@ -47,8 +53,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3548b8efc9f8e8a5a0a2808c5bd8451a9031b9e5b879a79590304ae928b0a70" dependencies = [ "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] @@ -117,16 +123,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] -name = "byteorder" -version = "1.3.4" +name = "bitvec" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "byteorder" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" [[package]] name = "bytes" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f8e949d755f9d79112b5bb46938e0ef9d3804a0b16dfab13aafcaa5f0fa72" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" [[package]] name = "cairo-rs" @@ -195,12 +213,12 @@ checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" [[package]] name = "ctor" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c88d9506e2e9230f6107701b7d8425f4cb3f6df108ec3042a26e936666da5" +checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" dependencies = [ - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] @@ -220,8 +238,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] @@ -282,6 +300,7 @@ dependencies = [ "log", "maplit", "nix 0.19.1", + "nom", "num", "pretty_assertions", "pretty_env_logger", @@ -306,15 +325,21 @@ checksum = "0c635fdc695a9cbf89115695b04c27c864fa1bf5a94a253798a7bd0752fad5e5" dependencies = [ "proc-macro-error", "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] -name = "futures" -version = "0.3.8" +name = "funty" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + +[[package]] +name = "futures" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" dependencies = [ "futures-channel", "futures-core", @@ -327,9 +352,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" dependencies = [ "futures-core", "futures-sink", @@ -337,15 +362,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" +checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" [[package]] name = "futures-executor" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" +checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" dependencies = [ "futures-core", "futures-task", @@ -354,42 +379,42 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" +checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" [[package]] name = "futures-macro" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" +checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" dependencies = [ "proc-macro-hack", "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] name = "futures-sink" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" +checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" [[package]] name = "futures-task" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" +checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" dependencies = [ "once_cell", ] [[package]] name = "futures-util" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" +checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" dependencies = [ "futures-channel", "futures-core", @@ -398,7 +423,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project", + "pin-project-lite", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -595,8 +620,8 @@ dependencies = [ "proc-macro-crate", "proc-macro-error", "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] @@ -713,9 +738,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] @@ -754,9 +779,9 @@ dependencies = [ [[package]] name = "inotify-sys" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4563555856585ab3180a5bf0b2f9f8d301a728462afffc8195b3f5394229c55" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" dependencies = [ "libc", ] @@ -804,10 +829,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "libc" -version = "0.2.81" +name = "lexical-core" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +checksum = "21f866863575d0e1d654fbeeabdc927292fdf862873dc3c96c6f753357e13374" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" [[package]] name = "lock_api" @@ -820,11 +858,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", ] [[package]] @@ -886,6 +924,19 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -1036,9 +1087,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ "cfg-if 1.0.0", "instant", @@ -1088,8 +1139,8 @@ dependencies = [ "phf_shared", "proc-macro-hack", "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] @@ -1101,31 +1152,11 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pin-project" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a83804639aad6ba65345661744708855f9fbcb71176ea8d28d05aeb11d975e7" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7bcc46b8f73443d15bc1c5fecbb315718491fa9187fa483f0e359323cde8b3a" -dependencies = [ - "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", -] - [[package]] name = "pin-project-lite" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36743d754ccdf9954c2e352ce2d4b106e024c814f6499c2dadff80da9a442d8" +checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" [[package]] name = "pin-utils" @@ -1184,8 +1215,8 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", "version_check", ] @@ -1196,7 +1227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", - "quote 1.0.8", + "quote 1.0.9", "version_check", ] @@ -1208,9 +1239,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" @@ -1235,13 +1266,19 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" [[package]] name = "quote" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.7.3" @@ -1295,15 +1332,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.57" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +dependencies = [ + "bitflags", +] [[package]] name = "regex" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" dependencies = [ "aho-corasick", "memchr", @@ -1313,9 +1353,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.21" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" [[package]] name = "roxmltree" @@ -1326,6 +1366,12 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1334,22 +1380,22 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.118" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.118" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" dependencies = [ "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] @@ -1385,9 +1431,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "smart-default" @@ -1396,8 +1442,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" dependencies = [ "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] @@ -1411,6 +1457,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.8.0" @@ -1437,8 +1489,8 @@ dependencies = [ "heck", "proc-macro-error", "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] @@ -1455,8 +1507,8 @@ checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" dependencies = [ "heck", "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] @@ -1472,12 +1524,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" dependencies = [ "proc-macro2", - "quote 1.0.8", + "quote 1.0.9", "unicode-xid 0.2.1", ] @@ -1505,6 +1557,12 @@ dependencies = [ "version-compare", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "termcolor" version = "1.1.2" @@ -1539,24 +1597,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" dependencies = [ "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] name = "thread_local" -version = "1.0.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] name = "tokio" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258221f566b6c803c7b4714abadc080172b272090cdc5e244a6d4dd13c3a6bd" +checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a" dependencies = [ "autocfg", "bytes", @@ -1574,20 +1632,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494" +checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" dependencies = [ "proc-macro2", - "quote 1.0.8", - "syn 1.0.58", + "quote 1.0.9", + "syn 1.0.60", ] [[package]] name = "tokio-stream" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4cdeb73537e63f98adcd73138af75e3f368ccaecffaa29d7eb61b9f5a440457" +checksum = "1981ad97df782ab506a1f43bf82c967326960d278acf3bf8279809648c3ff3ea" dependencies = [ "futures-core", "pin-project-lite", @@ -1596,9 +1654,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36135b7e7da911f5f8b9331209f7fab4cc13498f3fff52f72a710c78187e3148" +checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b" dependencies = [ "bytes", "futures-core", @@ -1606,7 +1664,6 @@ dependencies = [ "log", "pin-project-lite", "tokio", - "tokio-stream", ] [[package]] @@ -1712,6 +1769,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "x11" version = "2.18.2" diff --git a/Cargo.toml b/Cargo.toml index dc8241d..0df621b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,8 +55,9 @@ futures-core = "0.3" futures-util = "0.3" tokio-util = "0.6" -dyn-clone = "1.0" +nom = "6.1" +dyn-clone = "1.0" [target.'cfg(target_os="linux")'.dependencies] inotify = "0.9" diff --git a/docs/content/main/expression_language.md b/docs/content/main/expression_language.md new file mode 100644 index 0000000..9984d0e --- /dev/null +++ b/docs/content/main/expression_language.md @@ -0,0 +1,33 @@ ++++ +title = "Eww expressions" +slug = "Embedded eww expression language" +weight = 6 ++++ + +# The embedded Eww expression-language + +Within variable references, you can make use of a small, built-in expression language. +This can be used whereever you can use variable-references (`{{varname}}`). + +## Example + +```xml + + +Some math: {{12 + 2 * 10}} +``` + +## Syntax + +The expression language supports: +- simple mathematical operations (`+`, `-`, `*`, `/`, `%`) +- comparisons (`==`, `!=`, `>`, `<`) +- boolean operations (`||`, `&&`, `!`) +- conditionals (`if condition then 'value' else 'other value'`) +- numbers, strings, booleans and variable references (`12`, `'hi'`, `true`, `some_variable`) + - strings can contain other expressions again: `'foo {{some_variable}} bar'` + diff --git a/src/config/eww_config.rs b/src/config/eww_config.rs index 87df525..100ab99 100644 --- a/src/config/eww_config.rs +++ b/src/config/eww_config.rs @@ -121,6 +121,7 @@ impl RawEwwConfig { pub fn read_from_file>(path: P) -> Result { let result: Result<_> = try { let content = util::replace_env_var_references(std::fs::read_to_string(path.as_ref())?); + let content = content.replace("&", "&"); let document = roxmltree::Document::parse(&content).map_err(|e| anyhow!(e))?; let root_node = XmlNode::from(document.root_element()); let root_element = root_node.as_element()?; diff --git a/src/eww_state.rs b/src/eww_state.rs index 40bed5a..29f3db5 100644 --- a/src/eww_state.rs +++ b/src/eww_state.rs @@ -121,7 +121,7 @@ impl EwwState { .iter() .map(|element| match element { AttrValueElement::Primitive(primitive) => Ok(primitive.clone()), - AttrValueElement::VarRef(var_name) => self.lookup(var_name).cloned(), + AttrValueElement::Expr(expr) => expr.clone().eval(&self.variables_state), }) .collect() } diff --git a/src/main.rs b/src/main.rs index f846401..922a8e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ #![feature(trace_macros)] +#![feature(box_syntax)] +#![feature(or_patterns)] +#![feature(box_patterns)] #![feature(slice_concat_trait)] #![feature(result_cloned)] #![feature(iterator_fold_self)] diff --git a/src/value/attr_value.rs b/src/value/attr_value/attr_value.rs similarity index 71% rename from src/value/attr_value.rs rename to src/value/attr_value/attr_value.rs index 1c7545f..3bd0be0 100644 --- a/src/value/attr_value.rs +++ b/src/value/attr_value/attr_value.rs @@ -1,7 +1,9 @@ use anyhow::*; -use std::{collections::HashMap, iter::FromIterator}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fmt, iter::FromIterator}; -use super::*; +use super::super::*; /// A value assigned to an attribute in a widget. /// This can be a primitive String that contains any amount of variable @@ -9,6 +11,12 @@ use super::*; #[derive(Serialize, Deserialize, Clone, PartialEq, derive_more::Into, derive_more::From, Default)] pub struct AttrValue(Vec); +impl fmt::Display for AttrValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.iter().map(|x| format!("{}", x)).join("")) + } +} + impl fmt::Debug for AttrValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AttrValue({:?})", self.0) @@ -40,7 +48,7 @@ impl AttrValue { } pub fn var_refs(&self) -> impl Iterator { - self.0.iter().filter_map(|x| x.as_var_ref()) + self.0.iter().filter_map(|x| Some(x.as_expr()?.var_refs())).flatten() } /// resolve partially. @@ -48,12 +56,16 @@ impl AttrValue { /// If a referenced variable is not found in the given hashmap, returns the var-ref unchanged. pub fn resolve_one_level(self, variables: &HashMap) -> AttrValue { self.into_iter() - .flat_map(|entry| match entry { - AttrValueElement::VarRef(var_name) => match variables.get(&var_name) { - Some(value) => value.0.clone(), - _ => vec![AttrValueElement::VarRef(var_name)], - }, - _ => vec![entry], + .map(|entry| match entry { + AttrValueElement::Expr(expr) => AttrValueElement::Expr(expr.map_terminals_into(|child_expr| match child_expr { + AttrValueExpr::VarRef(var_name) => match variables.get(&var_name) { + Some(value) => AttrValueExpr::Literal(value.clone()), + None => AttrValueExpr::VarRef(var_name), + }, + other => other, + })), + + _ => entry, }) .collect() } @@ -65,10 +77,7 @@ impl AttrValue { self.into_iter() .map(|element| match element { AttrValueElement::Primitive(x) => Ok(x), - AttrValueElement::VarRef(var_name) => variables - .get(&var_name) - .cloned() - .with_context(|| format!("Unknown variable '{}' referenced", var_name)), + AttrValueElement::Expr(expr) => expr.eval(variables), }) .collect() } @@ -85,7 +94,7 @@ impl AttrValue { if c == '}' { curly_count -= 1; if curly_count == 0 { - elements.push(AttrValueElement::VarRef(VarName(std::mem::take(varref)))); + elements.push(AttrValueElement::Expr(AttrValueExpr::parse(varref).unwrap())); cur_varref = None } } else { @@ -120,14 +129,22 @@ impl AttrValue { #[derive(Clone, PartialEq, Serialize, Deserialize)] pub enum AttrValueElement { Primitive(PrimitiveValue), - VarRef(VarName), + Expr(AttrValueExpr), +} +impl fmt::Display for AttrValueElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AttrValueElement::Primitive(x) => write!(f, "{}", x), + AttrValueElement::Expr(x) => write!(f, "{{{{{}}}}}", x), + } + } } impl fmt::Debug for AttrValueElement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AttrValueElement::Primitive(x) => write!(f, "Primitive({:?})", x), - AttrValueElement::VarRef(x) => write!(f, "VarRef({:?})", x), + AttrValueElement::Expr(x) => write!(f, "Expr({:?})", x), } } } @@ -137,9 +154,9 @@ impl AttrValueElement { AttrValueElement::Primitive(PrimitiveValue::from_string(s)) } - pub fn as_var_ref(&self) -> Option<&VarName> { + pub fn as_expr(&self) -> Option<&AttrValueExpr> { match self { - AttrValueElement::VarRef(x) => Some(&x), + AttrValueElement::Expr(x) => Some(&x), _ => None, } } @@ -163,12 +180,12 @@ mod test { assert_eq!( output, AttrValue(vec![ - AttrValueElement::VarRef(VarName("foo".to_owned())), - AttrValueElement::VarRef(VarName("bar".to_owned())), + AttrValueElement::Expr(AttrValueExpr::VarRef(VarName("foo".to_owned()))), + AttrValueElement::Expr(AttrValueExpr::VarRef(VarName("bar".to_owned()))), AttrValueElement::primitive("b{}azb{a}z".to_owned()), - AttrValueElement::VarRef(VarName("bat".to_owned())), + AttrValueElement::Expr(AttrValueExpr::VarRef(VarName("bat".to_owned()))), AttrValueElement::primitive("{}quok".to_owned()), - AttrValueElement::VarRef(VarName("test".to_owned())), + AttrValueElement::Expr(AttrValueExpr::VarRef(VarName("test".to_owned()))), ]), ) } @@ -177,7 +194,7 @@ mod test { assert_eq!( AttrValue( vec![ - AttrValueElement::VarRef(VarName("var".to_owned())), + AttrValueElement::Expr(AttrValueExpr::VarRef(VarName("var".to_owned()))), AttrValueElement::primitive("something".to_owned()) ] .into() diff --git a/src/value/attr_value/attr_value_expr.rs b/src/value/attr_value/attr_value_expr.rs new file mode 100644 index 0000000..43bc7ee --- /dev/null +++ b/src/value/attr_value/attr_value_expr.rs @@ -0,0 +1,179 @@ +use super::super::*; +use anyhow::*; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub enum BinOp { + Plus, + Minus, + Times, + Div, + Mod, + Equals, + NotEquals, + And, + Or, + GT, + LT, +} + +impl std::fmt::Display for BinOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BinOp::Plus => write!(f, "+"), + BinOp::Minus => write!(f, "-"), + BinOp::Times => write!(f, "*"), + BinOp::Div => write!(f, "/"), + BinOp::Mod => write!(f, "%"), + BinOp::Equals => write!(f, "=="), + BinOp::NotEquals => write!(f, "!="), + BinOp::And => write!(f, "&&"), + BinOp::Or => write!(f, "||"), + BinOp::GT => write!(f, ">"), + BinOp::LT => write!(f, "<"), + } + } +} + +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub enum UnaryOp { + Not, +} + +impl std::fmt::Display for UnaryOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnaryOp::Not => write!(f, "!"), + } + } +} + +#[derive(Clone, PartialEq, Serialize, Deserialize)] +pub enum AttrValueExpr { + Literal(AttrValue), + VarRef(VarName), + BinOp(Box, BinOp, Box), + UnaryOp(UnaryOp, Box), + IfElse(Box, Box, Box), +} + +impl std::fmt::Display for AttrValueExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AttrValueExpr::VarRef(x) => write!(f, "{}", x), + AttrValueExpr::Literal(x) => write!(f, "\"{}\"", x), + AttrValueExpr::BinOp(l, op, r) => write!(f, "({} {} {})", l, op, r), + AttrValueExpr::UnaryOp(op, x) => write!(f, "{}{}", op, x), + AttrValueExpr::IfElse(a, b, c) => write!(f, "(if {} then {} else {})", a, b, c), + } + } +} + +impl std::fmt::Debug for AttrValueExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } +} + +impl AttrValueExpr { + pub fn map_terminals_into(self, f: impl Fn(Self) -> Self) -> Self { + use AttrValueExpr::*; + match self { + BinOp(box a, op, box b) => BinOp(box f(a), op, box f(b)), + IfElse(box a, box b, box c) => IfElse(box f(a), box f(b), box f(c)), + other => f(other), + } + } + + /// resolve variable references in the expression. Fails if a variable cannot be resolved. + pub fn resolve_refs(self, variables: &HashMap) -> Result { + use AttrValueExpr::*; + match self { + Literal(x) => Ok(AttrValueExpr::Literal(x)), + VarRef(ref name) => Ok(Literal(AttrValue::from_primitive( + variables + .get(name) + .with_context(|| format!("Unknown variable {} referenced in {:?}", &name, &self))? + .clone(), + ))), + BinOp(box a, op, box b) => Ok(BinOp(box a.resolve_refs(variables)?, op, box b.resolve_refs(variables)?)), + UnaryOp(op, box x) => Ok(UnaryOp(op, box x.resolve_refs(variables)?)), + IfElse(box a, box b, box c) => Ok(IfElse( + box a.resolve_refs(variables)?, + box b.resolve_refs(variables)?, + box c.resolve_refs(variables)?, + )), + } + } + + pub fn var_refs(&self) -> Vec<&VarName> { + use AttrValueExpr::*; + match self { + Literal(_) => vec![], + VarRef(name) => vec![name], + BinOp(box a, _, box b) => { + let mut refs = a.var_refs(); + refs.append(&mut b.var_refs()); + refs + } + UnaryOp(_, box x) => x.var_refs(), + IfElse(box a, box b, box c) => { + let mut refs = a.var_refs(); + refs.append(&mut b.var_refs()); + refs.append(&mut c.var_refs()); + refs + } + } + } + + pub fn eval(self, values: &HashMap) -> Result { + match self { + AttrValueExpr::Literal(x) => x.resolve_fully(&values), + AttrValueExpr::VarRef(ref name) => values.get(name).cloned().context(format!( + "Got unresolved variable {} while trying to evaluate expression {:?}", + &name, &self + )), + AttrValueExpr::BinOp(a, op, b) => { + let a = a.eval(values)?; + let b = b.eval(values)?; + Ok(match op { + BinOp::Equals => PrimitiveValue::from(a == b), + BinOp::NotEquals => PrimitiveValue::from(a != b), + BinOp::And => PrimitiveValue::from(a.as_bool()? && b.as_bool()?), + BinOp::Or => PrimitiveValue::from(a.as_bool()? || b.as_bool()?), + + BinOp::Plus => PrimitiveValue::from(a.as_f64()? + b.as_f64()?), + BinOp::Minus => PrimitiveValue::from(a.as_f64()? - b.as_f64()?), + BinOp::Times => PrimitiveValue::from(a.as_f64()? * b.as_f64()?), + BinOp::Div => PrimitiveValue::from(a.as_f64()? / b.as_f64()?), + BinOp::Mod => PrimitiveValue::from(a.as_f64()? % b.as_f64()?), + BinOp::GT => PrimitiveValue::from(a.as_f64()? > b.as_f64()?), + BinOp::LT => PrimitiveValue::from(a.as_f64()? < b.as_f64()?), + }) + } + AttrValueExpr::UnaryOp(op, a) => { + let a = a.eval(values)?; + Ok(match op { + UnaryOp::Not => PrimitiveValue::from(!a.as_bool()?), + }) + } + AttrValueExpr::IfElse(cond, yes, no) => { + if cond.eval(values)?.as_bool()? { + yes.eval(values) + } else { + no.eval(values) + } + } + } + } + + pub fn parse<'a>(s: &'a str) -> Result { + let parsed = match parser::parse(s) { + Ok((_, x)) => Ok(x), + Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(anyhow!(nom::error::convert_error(s, e))), + Err(nom::Err::Incomplete(_)) => Err(anyhow!("Parsing incomplete")), + }; + parsed.context("Failed to parse expression") + } +} diff --git a/src/value/attr_value/mod.rs b/src/value/attr_value/mod.rs new file mode 100644 index 0000000..077a689 --- /dev/null +++ b/src/value/attr_value/mod.rs @@ -0,0 +1,5 @@ +pub mod attr_value; +pub mod attr_value_expr; +pub mod parser; +pub use attr_value::*; +pub use attr_value_expr::*; diff --git a/src/value/attr_value/parser.rs b/src/value/attr_value/parser.rs new file mode 100644 index 0000000..14ca0d3 --- /dev/null +++ b/src/value/attr_value/parser.rs @@ -0,0 +1,209 @@ +use super::*; + +use nom::{ + branch::*, + bytes::complete::{tag, take_while}, + character::complete::{multispace0 as multispace, *}, + combinator::{map, map_res, *}, + error::{context, ParseError, VerboseError}, + multi::many0, + sequence::{delimited, preceded, *}, + IResult, Parser, +}; + +use super::super::*; + +fn ws<'a, P, O, E: ParseError<&'a str>>(p: P) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> +where + P: Parser<&'a str, O, E>, +{ + delimited(multispace, p, multispace) +} + +fn parse_num(i: &str) -> IResult<&str, i32, VerboseError<&str>> { + alt(( + map_res(digit1, |s: &str| s.parse::()), + map_res(preceded(tag("-"), digit1), |s: &str| s.parse::().map(|x| x * -1)), + ))(i) +} + +fn parse_stringlit(i: &str) -> IResult<&str, &str, VerboseError<&str>> { + alt(( + delimited(tag("'"), take_while(|c| c != '\''), tag("'")), + delimited(tag("\""), take_while(|c| c != '"'), tag("\"")), + ))(i) +} + +fn parse_bool(i: &str) -> IResult<&str, &str, VerboseError<&str>> { + alt((tag("true"), tag("false")))(i) +} + +fn parse_literal(i: &str) -> IResult<&str, &str, VerboseError<&str>> { + alt((parse_bool, parse_stringlit, recognize(parse_num)))(i) +} + +fn parse_identifier(i: &str) -> IResult<&str, &str, VerboseError<&str>> { + verify( + recognize(pair( + alt((alpha1, tag("_"), tag("-"))), + many0(alt((alphanumeric1, tag("_"), tag("-")))), + )), + |x| !["if", "then", "else"].contains(x), + )(i) +} + +fn parse_unary_op(i: &str) -> IResult<&str, UnaryOp, VerboseError<&str>> { + value(UnaryOp::Not, tag("!"))(i) +} + +///////////////// +// actual tree // +///////////////// + +fn parse_factor(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { + let (i, unary_op) = opt(parse_unary_op)(i)?; + let (i, factor) = alt(( + context("expression", ws(delimited(tag("("), parse_expr, tag(")")))), + context("if-expression", ws(parse_ifelse)), + context( + "literal", + map(ws(parse_literal), |x| AttrValueExpr::Literal(AttrValue::parse_string(x))), + ), + context( + "identifier", + map(ws(parse_identifier), |x| AttrValueExpr::VarRef(VarName(x.to_string()))), + ), + ))(i)?; + Ok(( + i, + match unary_op { + Some(op) => AttrValueExpr::UnaryOp(op, box factor), + None => factor, + }, + )) +} + +fn parse_term3(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { + let (i, initial) = parse_factor(i)?; + let (i, remainder) = many0(alt(( + map(preceded(tag("*"), parse_factor), |x| (BinOp::Times, x)), + map(preceded(tag("/"), parse_factor), |x| (BinOp::Div, x)), + map(preceded(tag("%"), parse_factor), |x| (BinOp::Mod, x)), + )))(i)?; + + let exprs = remainder + .into_iter() + .fold(initial, |acc, (op, expr)| AttrValueExpr::BinOp(box acc, op, box expr)); + + Ok((i, exprs)) +} +fn parse_term2(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { + let (i, initial) = parse_term3(i)?; + let (i, remainder) = many0(alt(( + map(preceded(tag("+"), parse_term3), |x| (BinOp::Plus, x)), + map(preceded(tag("-"), parse_term3), |x| (BinOp::Minus, x)), + )))(i)?; + + let exprs = remainder + .into_iter() + .fold(initial, |acc, (op, expr)| AttrValueExpr::BinOp(box acc, op, box expr)); + + Ok((i, exprs)) +} + +fn parse_term1(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { + let (i, initial) = parse_term2(i)?; + let (i, remainder) = many0(alt(( + map(preceded(tag("=="), parse_term2), |x| (BinOp::Equals, x)), + map(preceded(tag("!="), parse_term2), |x| (BinOp::NotEquals, x)), + map(preceded(tag(">"), parse_term2), |x| (BinOp::GT, x)), + map(preceded(tag("<"), parse_term2), |x| (BinOp::LT, x)), + )))(i)?; + + let exprs = remainder + .into_iter() + .fold(initial, |acc, (op, expr)| AttrValueExpr::BinOp(box acc, op, box expr)); + + Ok((i, exprs)) +} +pub fn parse_expr(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { + let (i, initial) = parse_term1(i)?; + let (i, remainder) = many0(alt(( + map(preceded(tag("&&"), parse_term1), |x| (BinOp::And, x)), + map(preceded(tag("||"), parse_term1), |x| (BinOp::Or, x)), + )))(i)?; + + let exprs = remainder + .into_iter() + .fold(initial, |acc, (op, expr)| AttrValueExpr::BinOp(box acc, op, box expr)); + + Ok((i, exprs)) +} + +fn parse_ifelse(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> { + let (i, _) = tag("if")(i)?; + let (i, a) = context("condition", ws(parse_expr))(i)?; + let (i, _) = tag("then")(i)?; + let (i, b) = context("true-case", ws(parse_expr))(i)?; + let (i, _) = tag("else")(i)?; + let (i, c) = context("false-case", ws(parse_expr))(i)?; + Ok((i, AttrValueExpr::IfElse(box a, box b, box c))) +} + +pub fn parse<'a>(i: &'a str) -> IResult<&'a str, AttrValueExpr, VerboseError<&'a str>> { + complete(parse_expr)(i) +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + #[test] + fn test_parser() { + assert_eq!( + AttrValueExpr::Literal(AttrValue::from_primitive("12")), + AttrValueExpr::parse("12").unwrap() + ); + assert_eq!( + AttrValueExpr::UnaryOp(UnaryOp::Not, box AttrValueExpr::Literal(AttrValue::from_primitive("false"))), + AttrValueExpr::parse("!false").unwrap() + ); + assert_eq!( + AttrValueExpr::BinOp( + box AttrValueExpr::Literal(AttrValue::from_primitive("12")), + BinOp::Plus, + box AttrValueExpr::Literal(AttrValue::from_primitive("2")) + ), + AttrValueExpr::parse("12 + 2").unwrap() + ); + assert_eq!( + AttrValueExpr::UnaryOp( + UnaryOp::Not, + box AttrValueExpr::BinOp( + box AttrValueExpr::Literal(AttrValue::from_primitive("1")), + BinOp::Equals, + box AttrValueExpr::Literal(AttrValue::from_primitive("2")) + ) + ), + AttrValueExpr::parse("!(1 == 2)").unwrap() + ); + assert_eq!( + AttrValueExpr::IfElse( + box AttrValueExpr::VarRef(VarName("a".to_string())), + box AttrValueExpr::VarRef(VarName("b".to_string())), + box AttrValueExpr::VarRef(VarName("c".to_string())), + ), + AttrValueExpr::parse("if a then b else c").unwrap() + ); + } + #[test] + fn test_complex() { + let parsed = + AttrValueExpr::parse(r#"if hi > 12 + 2 * 2 && 12 == 15 then "foo" else if !true then 'hi' else "{{bruh}}""#).unwrap(); + + assert_eq!( + r#"(if ((hi > ("12" + ("2" * "2"))) && ("12" == "15")) then "foo" else (if !"true" then "hi" else "{{bruh}}"))"#, + format!("{}", parsed), + ) + } +} diff --git a/src/value/mod.rs b/src/value/mod.rs index 5e9c239..cd002af 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,11 +1,11 @@ use derive_more::*; use serde::{Deserialize, Serialize}; -use std::fmt; pub mod attr_value; pub mod coords; pub mod primitive; pub use attr_value::*; +pub use attr_value_expr::*; pub use coords::*; pub use primitive::*; diff --git a/src/value/primitive.rs b/src/value/primitive.rs index 9dd6ba2..98891f1 100644 --- a/src/value/primitive.rs +++ b/src/value/primitive.rs @@ -5,7 +5,7 @@ use std::{convert::TryFrom, fmt, iter::FromIterator}; use crate::impl_try_from; -#[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From, Default)] +#[derive(Clone, Deserialize, Serialize, derive_more::From, Default)] pub struct PrimitiveValue(String); impl fmt::Display for PrimitiveValue { @@ -19,6 +19,17 @@ impl fmt::Debug for PrimitiveValue { } } +/// Manually implement equality, to allow for values in different formats (i.e. "1" and "1.0") to still be considered as equal. +impl std::cmp::PartialEq for PrimitiveValue { + fn eq(&self, other: &Self) -> bool { + if let (Ok(a), Ok(b)) = (self.as_f64(), other.as_f64()) { + a == b + } else { + self.0 == other.0 + } + } +} + impl FromIterator for PrimitiveValue { fn from_iter>(iter: T) -> Self { PrimitiveValue(iter.into_iter().join("")) @@ -54,6 +65,12 @@ impl From for PrimitiveValue { } } +impl From for PrimitiveValue { + fn from(s: f64) -> Self { + PrimitiveValue(s.to_string()) + } +} + impl From<&str> for PrimitiveValue { fn from(s: &str) -> Self { PrimitiveValue(s.to_string()) diff --git a/src/widgets/widget_node.rs b/src/widgets/widget_node.rs index 0fc98b7..a43263c 100644 --- a/src/widgets/widget_node.rs +++ b/src/widgets/widget_node.rs @@ -125,7 +125,6 @@ mod test { use super::*; use crate::config::xml_ext::*; use maplit::hashmap; - use pretty_assertions::assert_eq; #[test] fn test_generic_generate() { let w_def1 = {