From 0b958e7852e781dff0e4d571e7bae715690b4268 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 10 Jun 2024 23:09:13 -0700 Subject: [PATCH] initial public commit --- .gitignore | 2 + Cargo.lock | 593 ++++ Cargo.toml | 5 + LICENSE.md | 157 + Notices.txt | 16 + crates/fayalite-proc-macros-impl/Cargo.toml | 18 + crates/fayalite-proc-macros-impl/build.rs | 4 + crates/fayalite-proc-macros-impl/src/fold.rs | 248 ++ crates/fayalite-proc-macros-impl/src/lib.rs | 624 ++++ .../fayalite-proc-macros-impl/src/module.rs | 282 ++ .../src/module/transform_body.rs | 1566 +++++++++ .../expand_aggregate_literals.rs | 530 +++ .../src/module/transform_body/expand_match.rs | 625 ++++ .../src/value_derive_common.rs | 746 ++++ .../src/value_derive_enum.rs | 901 +++++ .../src/value_derive_struct.rs | 709 ++++ crates/fayalite-proc-macros/Cargo.toml | 14 + crates/fayalite-proc-macros/src/lib.rs | 20 + crates/fayalite-visit-gen/Cargo.toml | 18 + crates/fayalite-visit-gen/src/ast.rs | 613 ++++ crates/fayalite-visit-gen/src/lib.rs | 426 +++ crates/fayalite/Cargo.toml | 24 + crates/fayalite/build.rs | 15 + crates/fayalite/src/annotations.rs | 196 ++ crates/fayalite/src/array.rs | 729 ++++ crates/fayalite/src/bundle.rs | 796 +++++ crates/fayalite/src/clock.rs | 156 + crates/fayalite/src/enum_.rs | 620 ++++ crates/fayalite/src/expr.rs | 1090 ++++++ crates/fayalite/src/expr/ops.rs | 1590 +++++++++ crates/fayalite/src/firrtl.rs | 2470 +++++++++++++ crates/fayalite/src/int.rs | 1176 +++++++ crates/fayalite/src/intern.rs | 1076 ++++++ crates/fayalite/src/intern/type_map.rs | 138 + crates/fayalite/src/lib.rs | 27 + crates/fayalite/src/memory.rs | 1094 ++++++ crates/fayalite/src/module.rs | 2615 ++++++++++++++ crates/fayalite/src/module/transform.rs | 5 + .../src/module/transform/simplify_enums.rs | 654 ++++ .../src/module/transform/simplify_memories.rs | 940 +++++ crates/fayalite/src/module/transform/visit.rs | 462 +++ crates/fayalite/src/reg.rs | 151 + crates/fayalite/src/reset.rs | 359 ++ crates/fayalite/src/source_location.rs | 203 ++ crates/fayalite/src/ty.rs | 1007 ++++++ crates/fayalite/src/util.rs | 267 ++ crates/fayalite/src/valueless.rs | 89 + crates/fayalite/src/wire.rs | 112 + crates/fayalite/tests/module.rs | 3065 +++++++++++++++++ crates/fayalite/tests/ui.rs | 7 + crates/fayalite/tests/ui/module.rs | 14 + crates/fayalite/tests/ui/module.stderr | 17 + crates/fayalite/tests/ui/value_derive.rs | 10 + crates/fayalite/tests/ui/value_derive.stderr | 7 + crates/fayalite/tests/value_derive.rs | 23 + crates/fayalite/visit_types.json | 914 +++++ 56 files changed, 30235 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE.md create mode 100644 Notices.txt create mode 100644 crates/fayalite-proc-macros-impl/Cargo.toml create mode 100644 crates/fayalite-proc-macros-impl/build.rs create mode 100644 crates/fayalite-proc-macros-impl/src/fold.rs create mode 100644 crates/fayalite-proc-macros-impl/src/lib.rs create mode 100644 crates/fayalite-proc-macros-impl/src/module.rs create mode 100644 crates/fayalite-proc-macros-impl/src/module/transform_body.rs create mode 100644 crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs create mode 100644 crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs create mode 100644 crates/fayalite-proc-macros-impl/src/value_derive_common.rs create mode 100644 crates/fayalite-proc-macros-impl/src/value_derive_enum.rs create mode 100644 crates/fayalite-proc-macros-impl/src/value_derive_struct.rs create mode 100644 crates/fayalite-proc-macros/Cargo.toml create mode 100644 crates/fayalite-proc-macros/src/lib.rs create mode 100644 crates/fayalite-visit-gen/Cargo.toml create mode 100644 crates/fayalite-visit-gen/src/ast.rs create mode 100644 crates/fayalite-visit-gen/src/lib.rs create mode 100644 crates/fayalite/Cargo.toml create mode 100644 crates/fayalite/build.rs create mode 100644 crates/fayalite/src/annotations.rs create mode 100644 crates/fayalite/src/array.rs create mode 100644 crates/fayalite/src/bundle.rs create mode 100644 crates/fayalite/src/clock.rs create mode 100644 crates/fayalite/src/enum_.rs create mode 100644 crates/fayalite/src/expr.rs create mode 100644 crates/fayalite/src/expr/ops.rs create mode 100644 crates/fayalite/src/firrtl.rs create mode 100644 crates/fayalite/src/int.rs create mode 100644 crates/fayalite/src/intern.rs create mode 100644 crates/fayalite/src/intern/type_map.rs create mode 100644 crates/fayalite/src/lib.rs create mode 100644 crates/fayalite/src/memory.rs create mode 100644 crates/fayalite/src/module.rs create mode 100644 crates/fayalite/src/module/transform.rs create mode 100644 crates/fayalite/src/module/transform/simplify_enums.rs create mode 100644 crates/fayalite/src/module/transform/simplify_memories.rs create mode 100644 crates/fayalite/src/module/transform/visit.rs create mode 100644 crates/fayalite/src/reg.rs create mode 100644 crates/fayalite/src/reset.rs create mode 100644 crates/fayalite/src/source_location.rs create mode 100644 crates/fayalite/src/ty.rs create mode 100644 crates/fayalite/src/util.rs create mode 100644 crates/fayalite/src/valueless.rs create mode 100644 crates/fayalite/src/wire.rs create mode 100644 crates/fayalite/tests/module.rs create mode 100644 crates/fayalite/tests/ui.rs create mode 100644 crates/fayalite/tests/ui/module.rs create mode 100644 crates/fayalite/tests/ui/module.stderr create mode 100644 crates/fayalite/tests/ui/value_derive.rs create mode 100644 crates/fayalite/tests/ui/value_derive.stderr create mode 100644 crates/fayalite/tests/value_derive.rs create mode 100644 crates/fayalite/visit_types.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ccb5166 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8554f06 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,593 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "basic-toml" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "serde", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fayalite" +version = "0.1.0" +dependencies = [ + "bitvec", + "fayalite-proc-macros", + "fayalite-visit-gen", + "hashbrown", + "num-bigint", + "num-traits", + "paste", + "serde", + "serde_json", + "trybuild", +] + +[[package]] +name = "fayalite-proc-macros" +version = "0.1.0" +dependencies = [ + "fayalite-proc-macros-impl", +] + +[[package]] +name = "fayalite-proc-macros-impl" +version = "0.1.0" +dependencies = [ + "base16ct", + "num-bigint", + "prettyplease", + "proc-macro2", + "quote", + "sha2", + "syn", + "tempfile", +] + +[[package]] +name = "fayalite-visit-gen" +version = "0.1.0" +dependencies = [ + "indexmap", + "prettyplease", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "thiserror", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "serde" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "trybuild" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9d3ba662913483d6722303f619e75ea10b7855b0f8e0d72799cf8621bb488f" +dependencies = [ + "basic-toml", + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +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 = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..92a51dc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# See Notices.txt for copyright information +[workspace] +resolver = "2" +members = ["crates/*"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0927556 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,157 @@ +### GNU LESSER GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the +terms and conditions of version 3 of the GNU General Public License, +supplemented by the additional permissions listed below. + +#### 0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the +GNU General Public License. + +"The Library" refers to a covered work governed by this License, other +than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + +The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +#### 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +#### 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + +- a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or +- b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +#### 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a +header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + +- a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the object code with a copy of the GNU GPL and this + license document. + +#### 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + +- a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. +- c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. +- d) Do one of the following: + - 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + - 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. +- e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +#### 5. Combined Libraries. + +You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + +- a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. +- b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + +#### 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License "or any later version" applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, you +may choose any version of the GNU Lesser General Public License ever +published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/Notices.txt b/Notices.txt new file mode 100644 index 0000000..b495805 --- /dev/null +++ b/Notices.txt @@ -0,0 +1,16 @@ +Copyright 2024 Jacob Lifshay + +This file is part of Fayalite. + +Fayalite is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Fayalite is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with Fayalite. If not, see . diff --git a/crates/fayalite-proc-macros-impl/Cargo.toml b/crates/fayalite-proc-macros-impl/Cargo.toml new file mode 100644 index 0000000..4c94935 --- /dev/null +++ b/crates/fayalite-proc-macros-impl/Cargo.toml @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# See Notices.txt for copyright information +[package] +name = "fayalite-proc-macros-impl" +version = "0.1.0" +edition = "2021" +workspace = "../.." +license = "LGPL-3.0-or-later" + +[dependencies] +base16ct = "0.2.0" +num-bigint = "0.4.4" +prettyplease = "0.2.20" +proc-macro2 = "1.0.78" +quote = "1.0.35" +sha2 = "0.10.8" +syn = { version = "2.0.53", features = ["full", "fold", "visit", "extra-traits"] } +tempfile = "3.10.1" diff --git a/crates/fayalite-proc-macros-impl/build.rs b/crates/fayalite-proc-macros-impl/build.rs new file mode 100644 index 0000000..94ccd5e --- /dev/null +++ b/crates/fayalite-proc-macros-impl/build.rs @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +// build.rs to make cargo set env!("OUT_DIR") +fn main() {} diff --git a/crates/fayalite-proc-macros-impl/src/fold.rs b/crates/fayalite-proc-macros-impl/src/fold.rs new file mode 100644 index 0000000..02c1a2e --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/fold.rs @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +pub(crate) trait DoFold { + fn do_fold(self, state: &mut State) -> Self; +} + +impl, State: ?Sized + syn::fold::Fold> DoFold for Box { + fn do_fold(mut self, state: &mut State) -> Self { + *self = T::do_fold(*self, state); + self + } +} + +impl, State: ?Sized + syn::fold::Fold> DoFold for Option { + fn do_fold(self, state: &mut State) -> Self { + self.map(|v| T::do_fold(v, state)) + } +} + +impl, State: ?Sized + syn::fold::Fold> DoFold for Vec { + fn do_fold(self, state: &mut State) -> Self { + Vec::from_iter(self.into_iter().map(|v| T::do_fold(v, state))) + } +} + +impl, P: DoFold, State: ?Sized + syn::fold::Fold> DoFold + for Punctuated +{ + fn do_fold(self, state: &mut State) -> Self { + Punctuated::from_iter(self.into_pairs().map(|v| { + let (v, p) = v.into_tuple().do_fold(state); + Pair::new(v, p) + })) + } +} + +macro_rules! impl_fold_tuple { + ($($var0:ident: $T0:ident, $($var:ident: $T:ident,)*)?) => { + $(impl_fold_tuple!($($var: $T,)*);)? + impl_fold_tuple!(@impl $($var0: $T0, $($var: $T,)*)?); + }; + (@impl $($var:ident: $T:ident,)*) => { + impl,)*> DoFold for ($($T,)*) { + #[allow(clippy::unused_unit)] + fn do_fold(self, state: &mut State) -> Self { + let _ = state; + let ($($var,)*) = self; + $(let $var = $var.do_fold(state);)* + ($($var,)*) + } + } + }; +} + +impl_fold_tuple!( + v0: T0, + v1: T1, + v2: T2, + v3: T3, + v4: T4, + v5: T5, + v6: T6, + v7: T7, + v8: T8, + v9: T9, + v10: T10, + v11: T11, +); + +macro_rules! no_op_fold { + ($ty:ty) => { + impl $crate::fold::DoFold for $ty { + fn do_fold(self, _state: &mut State) -> Self { + self + } + } + }; +} + +pub(crate) use no_op_fold; + +macro_rules! impl_fold { + ( + struct $Struct:ident<$($T:ident,)*> $(where ($($where:tt)*))? { + $($field:ident: $field_ty:ty,)* + } + ) => { + impl $crate::fold::DoFold for $Struct<$($T,)*> + where + $($T: $crate::fold::DoFold,)* + $($where)* + { + fn do_fold(self, state: &mut State) -> Self { + let _ = state; + let Self { + $($field,)* + } = self; + Self { + $($field: <$field_ty as $crate::fold::DoFold>::do_fold($field, state),)* + } + } + } + }; + ( + struct $Struct:ident<$($T:ident,)*>( + $field0_ty:ty $(,)? + ) + $(where ($($where:tt)*))?; + ) => { + impl $crate::fold::DoFold for $Struct<$($T,)*> + where + $($T: $crate::fold::DoFold,)* + $($where)* + { + fn do_fold(self, state: &mut State) -> Self { + let _ = state; + let Self( + v0, + ) = self; + Self( + <$field0_ty as $crate::fold::DoFold>::do_fold(v0, state), + ) + } + } + }; + ( + enum $Enum:ident<$($T:ident,)*> $(where ($($where:tt)*))? { + $($Variant:ident $({ + $($brace_field:ident: $brace_field_ty:ty,)* + })? + $(( + $($paren_field_ty:ty),* $(,)? + ))?,)* + } + ) => { + impl $crate::fold::DoFold for $Enum<$($T,)*> + where + $($T: $crate::fold::DoFold,)* + $($where)* + { + fn do_fold(self, state: &mut State) -> Self { + let _ = state; + $crate::fold::impl_fold! { + @enum_variants self, state => () + $($Variant $({ + $($brace_field: $brace_field_ty,)* + })? + $(( + $($paren_field_ty,)* + ))?,)* + } + } + } + }; + ( + @enum_variants $self:expr, $state:expr => ($($generated_arms:tt)*) + ) => { + match $self { + $($generated_arms)* + } + }; + ( + @enum_variants $self:expr, $state:expr => ($($generated_arms:tt)*) + $Variant:ident { + $($field:tt: $field_ty:ty,)* + }, + $($rest:tt)* + ) => { + $crate::fold::impl_fold! { + @enum_variants $self, $state => ( + $($generated_arms)* + Self::$Variant { + $($field,)* + } => Self::$Variant { + $($field: <$field_ty as $crate::fold::DoFold>::do_fold($field, $state),)* + }, + ) + $($rest)* + } + }; + ( + @enum_variants $self:expr, $state:expr => ($($generated_arms:tt)*) + $Variant:ident( + $field0_ty:ty $(,)? + ), + $($rest:tt)* + ) => { + $crate::fold::impl_fold! { + @enum_variants $self, $state => ( + $($generated_arms)* + Self::$Variant(v0) => Self::$Variant( + <$field0_ty as $crate::fold::DoFold>::do_fold(v0, $state), + ), + ) + $($rest)* + } + }; +} + +pub(crate) use impl_fold; +use syn::punctuated::{Pair, Punctuated}; + +macro_rules! forward_fold { + ($ty:ty => $fn:ident) => { + impl DoFold for $ty { + fn do_fold(self, state: &mut State) -> Self { + ::$fn(state, self) + } + } + }; +} + +forward_fold!(syn::Attribute => fold_attribute); +forward_fold!(syn::AttrStyle => fold_attr_style); +forward_fold!(syn::Expr => fold_expr); +forward_fold!(syn::ExprArray => fold_expr_array); +forward_fold!(syn::ExprCall => fold_expr_call); +forward_fold!(syn::ExprIf => fold_expr_if); +forward_fold!(syn::ExprMatch => fold_expr_match); +forward_fold!(syn::ExprPath => fold_expr_path); +forward_fold!(syn::ExprStruct => fold_expr_struct); +forward_fold!(syn::ExprTuple => fold_expr_tuple); +forward_fold!(syn::Ident => fold_ident); +forward_fold!(syn::Member => fold_member); +forward_fold!(syn::Path => fold_path); +forward_fold!(syn::Type => fold_type); +forward_fold!(syn::TypePath => fold_type_path); +forward_fold!(syn::WherePredicate => fold_where_predicate); +no_op_fold!(syn::parse::Nothing); +no_op_fold!(syn::token::Brace); +no_op_fold!(syn::token::Bracket); +no_op_fold!(syn::token::Paren); +no_op_fold!(syn::Token![_]); +no_op_fold!(syn::Token![,]); +no_op_fold!(syn::Token![;]); +no_op_fold!(syn::Token![:]); +no_op_fold!(syn::Token![..]); +no_op_fold!(syn::Token![.]); +no_op_fold!(syn::Token![#]); +no_op_fold!(syn::Token![=]); +no_op_fold!(syn::Token![=>]); +no_op_fold!(syn::Token![|]); +no_op_fold!(syn::Token![enum]); +no_op_fold!(syn::Token![extern]); +no_op_fold!(syn::Token![let]); +no_op_fold!(syn::Token![mut]); +no_op_fold!(syn::Token![struct]); +no_op_fold!(syn::Token![where]); diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs new file mode 100644 index 0000000..cbcdd70 --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +#![cfg_attr(test, recursion_limit = "512")] +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use std::io::{ErrorKind, Write}; +use syn::{ + bracketed, parenthesized, + parse::{Parse, ParseStream, Parser}, + parse_quote, AttrStyle, Attribute, Error, Item, Token, +}; + +mod fold; +mod module; +mod value_derive_common; +mod value_derive_enum; +mod value_derive_struct; + +mod kw { + pub(crate) use syn::token::{ + Enum as enum_, Extern as extern_, Struct as struct_, Where as where_, + }; + + macro_rules! custom_keyword { + ($kw:ident) => { + syn::custom_keyword!($kw); + + impl quote::IdentFragment for $kw { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(stringify!($kw)) + } + + fn span(&self) -> Option { + Some(self.span) + } + } + + crate::fold::no_op_fold!($kw); + }; + } + + custom_keyword!(clock_domain); + custom_keyword!(connect_inexact); + custom_keyword!(fixed_type); + custom_keyword!(flip); + custom_keyword!(hdl); + custom_keyword!(input); + custom_keyword!(instance); + custom_keyword!(m); + custom_keyword!(memory); + custom_keyword!(memory_array); + custom_keyword!(memory_with_init); + custom_keyword!(no_reset); + custom_keyword!(outline_generated); + custom_keyword!(output); + custom_keyword!(reg_builder); + custom_keyword!(reset); + custom_keyword!(reset_default); + custom_keyword!(skip); + custom_keyword!(target); + custom_keyword!(wire); +} + +type Pound = Token![#]; // work around https://github.com/rust-lang/rust/issues/50676 + +#[derive(Clone, Debug)] +pub(crate) struct HdlAttr { + pub(crate) pound_token: Pound, + pub(crate) style: AttrStyle, + pub(crate) bracket_token: syn::token::Bracket, + pub(crate) hdl: kw::hdl, + pub(crate) paren_token: Option, + pub(crate) body: T, +} + +crate::fold::impl_fold! { + struct HdlAttr { + pound_token: Pound, + style: AttrStyle, + bracket_token: syn::token::Bracket, + hdl: kw::hdl, + paren_token: Option, + body: T, + } +} + +#[allow(dead_code)] +impl HdlAttr { + pub(crate) fn split_body(self) -> (HdlAttr<()>, T) { + let Self { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body, + } = self; + ( + HdlAttr { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body: (), + }, + body, + ) + } + pub(crate) fn replace_body(self, body: T2) -> HdlAttr { + let Self { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body: _, + } = self; + HdlAttr { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body, + } + } + pub(crate) fn as_ref(&self) -> HdlAttr<&T> { + let Self { + pound_token, + style, + bracket_token, + hdl, + paren_token, + ref body, + } = *self; + HdlAttr { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body, + } + } + pub(crate) fn try_map Result>(self, f: F) -> Result, E> { + let Self { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body, + } = self; + Ok(HdlAttr { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body: f(body)?, + }) + } + pub(crate) fn map R>(self, f: F) -> HdlAttr { + let Self { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body, + } = self; + HdlAttr { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body: f(body), + } + } + fn to_attr(&self) -> Attribute + where + T: ToTokens, + { + parse_quote! { #self } + } +} + +impl Default for HdlAttr { + fn default() -> Self { + T::default().into() + } +} + +impl From for HdlAttr { + fn from(body: T) -> Self { + HdlAttr { + pound_token: Default::default(), + style: AttrStyle::Outer, + bracket_token: Default::default(), + hdl: Default::default(), + paren_token: Default::default(), + body, + } + } +} + +impl ToTokens for HdlAttr { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.pound_token.to_tokens(tokens); + match self.style { + AttrStyle::Inner(style) => style.to_tokens(tokens), + AttrStyle::Outer => {} + }; + self.bracket_token.surround(tokens, |tokens| { + self.hdl.to_tokens(tokens); + match self.paren_token { + Some(paren_token) => { + paren_token.surround(tokens, |tokens| self.body.to_tokens(tokens)) + } + None => { + let body = self.body.to_token_stream(); + if !body.is_empty() { + syn::token::Paren(self.hdl.span) + .surround(tokens, |tokens| tokens.extend([body])); + } + } + } + }); + } +} + +fn is_hdl_attr(attr: &Attribute) -> bool { + attr.path().is_ident("hdl") +} + +impl HdlAttr { + fn parse_and_take_attr(attrs: &mut Vec) -> syn::Result> { + let mut retval = None; + let mut errors = Errors::new(); + attrs.retain(|attr| { + if is_hdl_attr(attr) { + if retval.is_some() { + errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute")); + } + errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v))); + false + } else { + true + } + }); + errors.finish()?; + Ok(retval) + } + fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result> { + let mut retval = None; + let mut errors = Errors::new(); + for attr in attrs { + if is_hdl_attr(attr) { + if retval.is_some() { + errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute")); + } + errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v))); + } + } + errors.finish()?; + Ok(retval) + } + fn parse_attr(attr: &Attribute) -> syn::Result { + match attr.style { + AttrStyle::Outer => Parser::parse2(Self::parse_outer, attr.to_token_stream()), + AttrStyle::Inner(_) => Parser::parse2(Self::parse_inner, attr.to_token_stream()), + } + } + fn parse_starting_with_brackets( + pound_token: Token![#], + style: AttrStyle, + input: ParseStream, + ) -> syn::Result { + let bracket_content; + let bracket_token = bracketed!(bracket_content in input); + let hdl = bracket_content.parse()?; + let paren_content; + let body; + let paren_token; + if bracket_content.is_empty() { + body = match syn::parse2(TokenStream::default()) { + Ok(body) => body, + Err(_) => { + parenthesized!(paren_content in bracket_content); + unreachable!(); + } + }; + paren_token = None; + } else { + paren_token = Some(parenthesized!(paren_content in bracket_content)); + body = paren_content.parse()?; + } + Ok(Self { + pound_token, + style, + bracket_token, + hdl, + paren_token, + body, + }) + } + fn parse_inner(input: ParseStream) -> syn::Result { + let pound_token = input.parse()?; + let style = AttrStyle::Inner(input.parse()?); + Self::parse_starting_with_brackets(pound_token, style, input) + } + fn parse_outer(input: ParseStream) -> syn::Result { + let pound_token = input.parse()?; + let style = AttrStyle::Outer; + Self::parse_starting_with_brackets(pound_token, style, input) + } +} + +pub(crate) struct Errors { + error: Option, + finished: bool, +} + +impl Drop for Errors { + fn drop(&mut self) { + if !std::thread::panicking() { + assert!(self.finished, "didn't run finish"); + } + } +} + +impl Errors { + pub(crate) fn new() -> Self { + Self { + error: None, + finished: false, + } + } + pub(crate) fn push(&mut self, e: Error) -> &mut Self { + match self.error { + Some(ref mut old) => old.combine(e), + None => self.error = Some(e), + } + self + } + pub(crate) fn push_result(&mut self, e: syn::Result<()>) -> &mut Self { + self.ok(e); + self + } + pub(crate) fn error( + &mut self, + tokens: impl ToTokens, + message: impl std::fmt::Display, + ) -> &mut Self { + self.push(Error::new_spanned(tokens, message)); + self + } + pub(crate) fn ok(&mut self, v: syn::Result) -> Option { + match v { + Ok(v) => Some(v), + Err(e) => { + self.push(e); + None + } + } + } + pub(crate) fn unwrap_or_else( + &mut self, + v: syn::Result, + fallback: impl FnOnce() -> T, + ) -> T { + match v { + Ok(v) => v, + Err(e) => { + self.push(e); + fallback() + } + } + } + pub(crate) fn unwrap_or(&mut self, v: syn::Result, fallback: T) -> T { + self.unwrap_or_else(v, || fallback) + } + pub(crate) fn unwrap_or_default(&mut self, v: syn::Result) -> T { + self.unwrap_or_else(v, T::default) + } + pub(crate) fn finish(&mut self) -> syn::Result<()> { + self.finished = true; + match self.error.take() { + Some(e) => Err(e), + None => Ok(()), + } + } +} + +impl Default for Errors { + fn default() -> Self { + Self::new() + } +} + +macro_rules! impl_extra_traits_for_options { + ( + #[no_ident_fragment] + $enum_vis:vis enum $option_enum_name:ident { + $($Variant:ident($key:ident),)* + } + ) => { + impl Copy for $option_enum_name {} + }; + ( + $enum_vis:vis enum $option_enum_name:ident { + $($Variant:ident($key:ident),)* + } + ) => { + impl Copy for $option_enum_name {} + + impl quote::IdentFragment for $option_enum_name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let _ = f; + match *self { + $(Self::$Variant(ref v) => quote::IdentFragment::fmt(&v.0, f),)* + } + } + + fn span(&self) -> Option { + match *self { + $(Self::$Variant(ref v) => quote::IdentFragment::span(&v.0),)* + } + } + } + + impl $option_enum_name { + #[allow(dead_code)] + $enum_vis fn span(&self) -> proc_macro2::Span { + quote::IdentFragment::span(self).unwrap() + } + } + }; + ( + $(#[no_ident_fragment])? + $enum_vis:vis enum $option_enum_name:ident { + $($Variant:ident($key:ident $(, $value:ty)?),)* + } + ) => {}; +} + +pub(crate) use impl_extra_traits_for_options; + +macro_rules! options { + ( + #[options = $options_name:ident] + $(#[$($enum_meta:tt)*])* + $enum_vis:vis enum $option_enum_name:ident { + $($Variant:ident($key:ident $(, $value:ty)?),)* + } + ) => { + crate::options! { + $(#[$($enum_meta)*])* + $enum_vis enum $option_enum_name { + $($Variant($key $(, $value)?),)* + } + } + + #[derive(Clone, Debug, Default)] + $enum_vis struct $options_name { + $($enum_vis $key: Option<(crate::kw::$key, $(syn::token::Paren, $value)?)>,)* + } + + crate::fold::impl_fold! { + struct $options_name<> { + $($key: Option<(crate::kw::$key, $(syn::token::Paren, $value)?)>,)* + } + } + + impl syn::parse::Parse for $options_name { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + #![allow(unused_mut, unused_variables, unreachable_code)] + let mut retval = Self::default(); + while !input.is_empty() { + let old_input = input.fork(); + match input.parse::<$option_enum_name>()? { + $($option_enum_name::$Variant(v) => { + if retval.$key.replace(v).is_some() { + return Err(old_input.error(concat!("duplicate ", stringify!($key), " option"))); + } + })* + } + if input.is_empty() { + break; + } + input.parse::()?; + } + Ok(retval) + } + } + + impl quote::ToTokens for $options_name { + #[allow(unused_mut, unused_variables, unused_assignments)] + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let mut separator: Option = None; + $(if let Some(v) = &self.$key { + separator.to_tokens(tokens); + separator = Some(Default::default()); + v.0.to_tokens(tokens); + $(v.1.surround(tokens, |tokens| <$value as quote::ToTokens>::to_tokens(&v.2, tokens));)? + })* + } + } + }; + ( + $(#[$($enum_meta:tt)*])* + $enum_vis:vis enum $option_enum_name:ident { + $($Variant:ident($key:ident $(, $value:ty)?),)* + } + ) => { + #[derive(Clone, Debug)] + $enum_vis enum $option_enum_name { + $($Variant((crate::kw::$key, $(syn::token::Paren, $value)?)),)* + } + + crate::impl_extra_traits_for_options! { + $(#[$($enum_meta)*])* + $enum_vis enum $option_enum_name { + $($Variant($key $(, $value)?),)* + } + } + + crate::fold::impl_fold! { + enum $option_enum_name<> { + $($Variant((crate::kw::$key, $(syn::token::Paren, $value)?)),)* + } + } + + impl syn::parse::Parse for $option_enum_name { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + $( + if lookahead.peek(crate::kw::$key) { + #[allow(unused_variables)] + let paren_content: syn::parse::ParseBuffer; + return Ok($option_enum_name::$Variant(( + input.parse()?, + $( + syn::parenthesized!(paren_content in input), + paren_content.parse::<$value>()?, + )? + ))); + } + )* + Err(lookahead.error()) + } + } + + impl quote::ToTokens for $option_enum_name { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let _ = tokens; + match *self { + $($option_enum_name::$Variant(ref v) => { + v.0.to_tokens(tokens); + $( + let value: &$value = &v.2; + v.1.surround(tokens, |tokens| value.to_tokens(tokens)); + )? + })* + } + } + } + }; +} + +pub(crate) use options; + +pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStream { + let out_dir = env!("OUT_DIR"); + let mut file = tempfile::Builder::new() + .prefix(prefix) + .rand_bytes(6) + .suffix(".tmp.rs") + .tempfile_in(out_dir) + .unwrap(); + let contents = prettyplease::unparse(&parse_quote! { #contents }); + let hash = ::digest(&contents); + let hash = base16ct::HexDisplay(&hash[..5]); + file.write_all(contents.as_bytes()).unwrap(); + let dest_file = std::path::Path::new(out_dir).join(format!("{prefix}{hash:x}.rs")); + // don't write if it already exists so cargo doesn't try to recompile constantly. + match file.persist_noclobber(&dest_file) { + Err(e) if e.error.kind() == ErrorKind::AlreadyExists => {} + e => { + e.unwrap(); + } + } + eprintln!("generated {}", dest_file.display()); + let dest_file = dest_file.to_str().unwrap(); + + quote! { + include!(#dest_file); + } +} + +pub fn module(attr: TokenStream, item: TokenStream) -> syn::Result { + let options = syn::parse2::(attr)?; + let options = HdlAttr::from(options); + let func = syn::parse2::(quote! { #options #item })?; + let mut contents = func.generate(); + if options.body.outline_generated.is_some() { + contents = outline_generated(contents, "module-"); + } + Ok(contents) +} + +pub fn value_derive(item: TokenStream) -> syn::Result { + let item = syn::parse2::(item)?; + match item { + Item::Enum(item) => value_derive_enum::value_derive_enum(item), + Item::Struct(item) => value_derive_struct::value_derive_struct(item), + _ => Err(syn::Error::new( + Span::call_site(), + "derive(Value) can only be used on structs or enums", + )), + } +} diff --git a/crates/fayalite-proc-macros-impl/src/module.rs b/crates/fayalite-proc-macros-impl/src/module.rs new file mode 100644 index 0000000..a84bc71 --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/module.rs @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + is_hdl_attr, + module::transform_body::{HdlLet, HdlLetKindIO}, + options, Errors, HdlAttr, +}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + parse_quote, + visit::{visit_pat, Visit}, + Attribute, Block, Error, FnArg, Ident, ItemFn, ItemStruct, ReturnType, Signature, Visibility, +}; + +mod transform_body; + +options! { + #[options = ConfigOptions] + #[no_ident_fragment] + pub(crate) enum ConfigOption { + OutlineGenerated(outline_generated), + Extern(extern_), + } +} + +options! { + pub(crate) enum ModuleIOKind { + Input(input), + Output(output), + } +} + +pub(crate) fn check_name_conflicts_with_module_builder(name: &Ident) -> syn::Result<()> { + if name == "m" { + Err(Error::new_spanned( + name, + "name conflicts with implicit `m: &mut ModuleBuilder<_>`", + )) + } else { + Ok(()) + } +} + +pub(crate) struct CheckNameConflictsWithModuleBuilderVisitor<'a> { + pub(crate) errors: &'a mut Errors, +} + +impl Visit<'_> for CheckNameConflictsWithModuleBuilderVisitor<'_> { + // TODO: change this to only check for identifiers defining new variables + fn visit_ident(&mut self, node: &Ident) { + self.errors + .push_result(check_name_conflicts_with_module_builder(node)); + } +} + +fn retain_struct_attrs bool>(item: &mut ItemStruct, mut f: F) { + item.attrs.retain(&mut f); + for field in item.fields.iter_mut() { + field.attrs.retain(&mut f); + } +} + +pub(crate) type ModuleIO = HdlLet; + +pub(crate) struct ModuleFn { + attrs: Vec, + config_options: HdlAttr, + module_kind: ModuleKind, + vis: Visibility, + sig: Signature, + block: Box, + io: Vec, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub(crate) enum ModuleKind { + Extern, + Normal, +} + +impl Parse for ModuleFn { + fn parse(input: ParseStream) -> syn::Result { + let ItemFn { + mut attrs, + vis, + mut sig, + block, + } = input.parse()?; + let Signature { + ref constness, + ref asyncness, + ref unsafety, + ref abi, + fn_token: _, + ident: _, + ref generics, + paren_token: _, + ref mut inputs, + ref variadic, + ref output, + } = sig; + let mut errors = Errors::new(); + let config_options = errors + .unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs)) + .unwrap_or_default(); + let ConfigOptions { + outline_generated: _, + extern_, + } = config_options.body; + let module_kind = match extern_ { + Some(_) => ModuleKind::Extern, + None => ModuleKind::Normal, + }; + for fn_arg in inputs { + match fn_arg { + FnArg::Receiver(_) => { + errors.push(syn::Error::new_spanned(fn_arg, "self not allowed here")); + } + FnArg::Typed(fn_arg) => { + visit_pat( + &mut CheckNameConflictsWithModuleBuilderVisitor { + errors: &mut errors, + }, + &fn_arg.pat, + ); + } + } + } + if let Some(constness) = constness { + errors.push(syn::Error::new_spanned(constness, "const not allowed here")); + } + if let Some(asyncness) = asyncness { + errors.push(syn::Error::new_spanned(asyncness, "async not allowed here")); + } + if let Some(unsafety) = unsafety { + errors.push(syn::Error::new_spanned(unsafety, "unsafe not allowed here")); + } + if let Some(abi) = abi { + errors.push(syn::Error::new_spanned(abi, "extern not allowed here")); + } + if !generics.params.is_empty() { + errors.push(syn::Error::new_spanned( + &generics.params, + "generics are not supported yet", + )); + } + if let Some(variadic) = variadic { + errors.push(syn::Error::new_spanned(variadic, "... not allowed here")); + } + if !matches!(output, ReturnType::Default) { + errors.push(syn::Error::new_spanned( + output, + "return type not allowed here", + )); + } + let body_results = errors.ok(transform_body::transform_body(module_kind, block)); + errors.finish()?; + let (block, io) = body_results.unwrap(); + Ok(Self { + attrs, + config_options, + module_kind, + vis, + sig, + block, + io, + }) + } +} + +impl ModuleFn { + pub(crate) fn generate(self) -> TokenStream { + let Self { + attrs, + config_options, + module_kind, + vis, + sig, + block, + io, + } = self; + let ConfigOptions { + outline_generated: _, + extern_: _, + } = config_options.body; + let mut outer_sig = sig.clone(); + let mut body_sig = sig; + let param_names = + Vec::from_iter(outer_sig.inputs.iter_mut().enumerate().map(|(index, arg)| { + let FnArg::Typed(arg) = arg else { + unreachable!("already checked"); + }; + let name = if let syn::Pat::Ident(pat) = &*arg.pat { + pat.ident.clone() + } else { + format_ident!("__param{}", index) + }; + *arg.pat = syn::Pat::Ident(syn::PatIdent { + attrs: vec![], + by_ref: None, + mutability: None, + ident: name.clone(), + subpat: None, + }); + name + })); + let module_kind_ty = match module_kind { + ModuleKind::Extern => quote! { ::fayalite::module::ExternModule }, + ModuleKind::Normal => quote! { ::fayalite::module::NormalModule }, + }; + let fn_name = &outer_sig.ident; + body_sig.ident = parse_quote! {__body}; + body_sig.inputs.insert( + 0, + parse_quote! {m: &mut ::fayalite::module::ModuleBuilder<#fn_name, #module_kind_ty>}, + ); + let body_fn = ItemFn { + attrs: vec![], + vis: Visibility::Inherited, + sig: body_sig, + block, + }; + outer_sig.output = + parse_quote! {-> ::fayalite::intern::Interned<::fayalite::module::Module<#fn_name>>}; + let io_flips = io + .iter() + .map(|io| match io.kind.kind { + ModuleIOKind::Input((input,)) => quote_spanned! {input.span=> + #[hdl(flip)] + }, + ModuleIOKind::Output(_) => quote! {}, + }) + .collect::>(); + let io_types = io.iter().map(|io| &io.kind.ty).collect::>(); + let io_names = io.iter().map(|io| &io.name).collect::>(); + let fn_name_str = fn_name.to_string(); + let block = parse_quote! {{ + #body_fn + ::fayalite::module::ModuleBuilder::run(#fn_name_str, |m| __body(m, #(#param_names,)*)) + }}; + let fixed_type = io.iter().all(|io| io.kind.ty_expr.is_none()); + let struct_options = if fixed_type { + quote! { #[hdl(fixed_type)] } + } else { + quote! {} + }; + let the_struct: ItemStruct = parse_quote! { + #[derive(::fayalite::__std::clone::Clone, + ::fayalite::__std::hash::Hash, + ::fayalite::__std::cmp::PartialEq, + ::fayalite::__std::cmp::Eq, + ::fayalite::__std::fmt::Debug)] + #[allow(non_camel_case_types)] + #struct_options + #vis struct #fn_name { + #( + #io_flips + #vis #io_names: #io_types,)* + } + }; + let mut struct_without_hdl_attrs = the_struct.clone(); + let mut struct_without_derives = the_struct; + retain_struct_attrs(&mut struct_without_hdl_attrs, |attr| !is_hdl_attr(attr)); + retain_struct_attrs(&mut struct_without_derives, |attr| { + !attr.path().is_ident("derive") + }); + let outer_fn = ItemFn { + attrs, + vis, + sig: outer_sig, + block, + }; + let mut retval = outer_fn.into_token_stream(); + struct_without_hdl_attrs.to_tokens(&mut retval); + retval.extend( + crate::value_derive_struct::value_derive_struct(struct_without_derives).unwrap(), + ); + retval + } +} diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs new file mode 100644 index 0000000..621aa7a --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -0,0 +1,1566 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + fold::{impl_fold, DoFold}, + is_hdl_attr, kw, + module::{check_name_conflicts_with_module_builder, ModuleIO, ModuleIOKind, ModuleKind}, + options, Errors, HdlAttr, +}; +use num_bigint::{BigInt, Sign}; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned, ToTokens}; +use std::{borrow::Borrow, convert::Infallible}; +use syn::{ + fold::{fold_expr, fold_expr_lit, fold_expr_unary, fold_local, fold_stmt, Fold}, + parenthesized, + parse::{Nothing, Parse, ParseStream}, + parse_quote, parse_quote_spanned, + spanned::Spanned, + token::Paren, + Attribute, Block, Error, Expr, ExprIf, ExprLet, ExprLit, ExprRepeat, ExprUnary, + GenericArgument, Ident, Item, Lit, LitStr, Local, LocalInit, Pat, Token, Type, UnOp, +}; + +mod expand_aggregate_literals; +mod expand_match; + +options! { + pub(crate) enum LetFnKind { + Input(input), + Output(output), + Instance(instance), + RegBuilder(reg_builder), + Wire(wire), + Memory(memory), + MemoryArray(memory_array), + MemoryWithInit(memory_with_init), + } +} + +macro_rules! with_debug_clone_and_fold { + ( + $(#[$meta:meta])* + $struct_vis:vis struct $name:ident<$($T:ident $(= $default_ty:ty)?),*> + $(where {$($where:tt)*})? + { + $($field_vis:vis $field:ident: $field_ty:ty,)* + } + ) => { + $(#[$meta])* + $struct_vis struct $name<$($T $(= $default_ty)?),*> + $(where $($where)*)? + { + $($field_vis $field: $field_ty,)* + } + + crate::fold::impl_fold! { + struct $name<$($T,)*> + $(where ($($where)*))? + { + $($field: $field_ty,)* + } + } + + // workaround for #[derive()] not working with generic structs with type macros + impl<$($T: std::fmt::Debug),*> std::fmt::Debug for $name<$($T),*> + $(where $($where)*)? + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(stringify!($name)) + $(.field(stringify!($field), &self.$field))* + .finish() + } + } + + impl<$($T: Clone),*> Clone for $name<$($T),*> + $(where $($where)*)? + { + fn clone(&self) -> Self { + Self { + $($field: self.$field.clone(),)* + } + } + } + }; +} + +pub(crate) use with_debug_clone_and_fold; + +with_debug_clone_and_fold! { + pub(crate) struct HdlLetKindIO { + pub(crate) colon_token: Token![:], + pub(crate) ty: Box, + pub(crate) m: kw::m, + pub(crate) dot_token: Token![.], + pub(crate) kind: Kind, + pub(crate) paren: Paren, + pub(crate) ty_expr: Option>, + } +} + +pub(crate) fn parse_single_fn_arg(input: ParseStream) -> syn::Result> { + let retval = input.parse()?; + let _: Option = input.parse()?; + Ok(retval) +} + +pub(crate) fn parse_optional_fn_arg(input: ParseStream) -> syn::Result>> { + if input.is_empty() { + return Ok(None); + } + parse_single_fn_arg(input).map(Some) +} + +impl HdlLetKindToTokens for HdlLetKindIO { + fn ty_to_tokens(&self, tokens: &mut TokenStream) { + let Self { + colon_token, + ty, + m: _, + dot_token: _, + kind: _, + paren: _, + ty_expr: _, + } = self; + colon_token.to_tokens(tokens); + ty.to_tokens(tokens); + } + + fn expr_to_tokens(&self, tokens: &mut TokenStream) { + let Self { + colon_token: _, + ty: _, + m, + dot_token, + kind, + paren, + ty_expr, + } = self; + m.to_tokens(tokens); + dot_token.to_tokens(tokens); + kind.to_tokens(tokens); + paren.surround(tokens, |tokens| ty_expr.to_tokens(tokens)); + } +} + +#[derive(Clone, Debug)] +pub(crate) struct HdlLetKindInstance { + pub(crate) m: kw::m, + pub(crate) dot_token: Token![.], + pub(crate) instance: kw::instance, + pub(crate) paren: Paren, + pub(crate) module: Box, +} + +impl_fold! { + struct HdlLetKindInstance<> { + m: kw::m, + dot_token: Token![.], + instance: kw::instance, + paren: Paren, + module: Box, + } +} + +impl HdlLetKindToTokens for HdlLetKindInstance { + fn ty_to_tokens(&self, _tokens: &mut TokenStream) {} + + fn expr_to_tokens(&self, tokens: &mut TokenStream) { + let Self { + m, + dot_token, + instance, + paren, + module, + } = self; + m.to_tokens(tokens); + dot_token.to_tokens(tokens); + instance.to_tokens(tokens); + paren.surround(tokens, |tokens| module.to_tokens(tokens)); + } +} + +#[derive(Clone, Debug)] +pub(crate) struct RegBuilderClockDomain { + pub(crate) dot_token: Token![.], + pub(crate) clock_domain: kw::clock_domain, + pub(crate) paren: Paren, + pub(crate) expr: Box, +} + +impl_fold! { + struct RegBuilderClockDomain<> { + dot_token: Token![.], + clock_domain: kw::clock_domain, + paren: Paren, + expr: Box, + } +} + +impl Parse for RegBuilderClockDomain { + fn parse(input: ParseStream) -> syn::Result { + let in_parens; + Ok(Self { + dot_token: input.parse()?, + clock_domain: input.parse()?, + paren: parenthesized!(in_parens in input), + expr: in_parens.call(parse_single_fn_arg)?, + }) + } +} + +impl ToTokens for RegBuilderClockDomain { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + dot_token, + clock_domain, + paren, + expr, + } = self; + dot_token.to_tokens(tokens); + clock_domain.to_tokens(tokens); + paren.surround(tokens, |tokens| expr.to_tokens(tokens)); + } +} + +#[derive(Clone, Debug)] +pub(crate) enum RegBuilderReset { + NoReset { + dot_token: Token![.], + no_reset: kw::no_reset, + paren: Paren, + ty_expr: Option>, + }, + Reset { + dot_token: Token![.], + reset: kw::reset, + paren: Paren, + init_expr: Box, + }, + ResetDefault { + dot_token: Token![.], + reset_default: kw::reset_default, + paren: Paren, + }, +} + +impl_fold! { + enum RegBuilderReset<> { + NoReset { + dot_token: Token![.], + no_reset: kw::no_reset, + paren: Paren, + ty_expr: Option>, + }, + Reset { + dot_token: Token![.], + reset: kw::reset, + paren: Paren, + init_expr: Box, + }, + ResetDefault { + dot_token: Token![.], + reset_default: kw::reset_default, + paren: Paren, + }, + } +} + +impl Parse for RegBuilderReset { + fn parse(input: ParseStream) -> syn::Result { + let dot_token = input.parse()?; + let paren_contents; + match RegBuilderMethod::parse(input, false, true)? { + RegBuilderMethod::ClockDomain(_) => unreachable!(), + RegBuilderMethod::NoReset(no_reset) => Ok(Self::NoReset { + dot_token, + no_reset, + paren: parenthesized!(paren_contents in input), + ty_expr: paren_contents.call(parse_optional_fn_arg)?, + }), + RegBuilderMethod::Reset(reset) => Ok(Self::Reset { + dot_token, + reset, + paren: parenthesized!(paren_contents in input), + init_expr: paren_contents.call(parse_single_fn_arg)?, + }), + RegBuilderMethod::ResetDefault(reset_default) => Ok(Self::ResetDefault { + dot_token, + reset_default, + paren: parenthesized!(paren_contents in input), + }), + } + } +} + +impl ToTokens for RegBuilderReset { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + RegBuilderReset::NoReset { + dot_token, + no_reset, + paren, + ty_expr, + } => { + dot_token.to_tokens(tokens); + no_reset.to_tokens(tokens); + paren.surround(tokens, |tokens| ty_expr.to_tokens(tokens)); + } + RegBuilderReset::Reset { + dot_token, + reset, + paren, + init_expr, + } => { + dot_token.to_tokens(tokens); + reset.to_tokens(tokens); + paren.surround(tokens, |tokens| init_expr.to_tokens(tokens)); + } + RegBuilderReset::ResetDefault { + dot_token, + reset_default, + paren, + } => { + dot_token.to_tokens(tokens); + reset_default.to_tokens(tokens); + paren.surround(tokens, |_| {}); + } + } + } +} + +macro_rules! make_builder_method_enum { + ( + #[parse_args($($parse_arg:ident: $parse_arg_ty:ty),+)] + $vis:vis enum $enum_name:ident { + $(#[cond = $cond:expr] + $variant:ident($kw:ident),)+ + } + ) => { + #[derive(Clone, Debug)] + $vis enum $enum_name { + $( + #[allow(dead_code)] + $variant(kw::$kw), + )+ + } + + impl $enum_name { + $vis fn parse(input: ParseStream, $($parse_arg: $parse_arg_ty),+) -> syn::Result { + let lookahead = input.lookahead1(); + $(if $cond && lookahead.peek(kw::$kw) { + return input.parse().map(Self::$variant) + })+ + Err(lookahead.error()) + } + $vis fn parse_dot_prefixed(input: ParseStream, $($parse_arg: $parse_arg_ty),+) -> syn::Result<(Token![.], Self)> { + let dot = input.parse()?; + Ok((dot, Self::parse(input, $($parse_arg),+)?)) + } + } + }; +} + +make_builder_method_enum! { + #[parse_args(need_clock_domain: bool, need_reset: bool)] + pub(crate) enum RegBuilderMethod { + #[cond = need_clock_domain] + ClockDomain(clock_domain), + #[cond = need_reset] + NoReset(no_reset), + #[cond = need_reset] + Reset(reset), + #[cond = need_reset] + ResetDefault(reset_default), + } +} + +#[derive(Clone, Debug)] +pub(crate) struct HdlLetKindRegBuilder { + pub(crate) ty: Option<(Token![:], Box)>, + pub(crate) m: kw::m, + pub(crate) dot_token: Token![.], + pub(crate) reg_builder: kw::reg_builder, + pub(crate) reg_builder_paren: Paren, + pub(crate) clock_domain: Option, + pub(crate) reset: RegBuilderReset, +} + +impl_fold! { + struct HdlLetKindRegBuilder<> { + ty: Option<(Token![:], Box)>, + m: kw::m, + dot_token: Token![.], + reg_builder: kw::reg_builder, + reg_builder_paren: Paren, + clock_domain: Option, + reset: RegBuilderReset, + } +} + +impl HdlLetKindRegBuilder { + fn rest_of_parse( + input: ParseStream, + parsed_ty: Option<(Token![:], Box)>, + _after_ty: Token![=], + m: kw::m, + dot_token: Token![.], + reg_builder: kw::reg_builder, + ) -> syn::Result { + let _reg_builder_paren_inner; + let reg_builder_paren = parenthesized!(_reg_builder_paren_inner in input); + let mut clock_domain = None; + match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, true)?.1 { + RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?), + RegBuilderMethod::NoReset(_) + | RegBuilderMethod::Reset(_) + | RegBuilderMethod::ResetDefault(_) => {} + } + let reset = input.parse()?; + if clock_domain.is_none() { + match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, false)?.1 { + RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?), + RegBuilderMethod::NoReset(_) + | RegBuilderMethod::Reset(_) + | RegBuilderMethod::ResetDefault(_) => unreachable!(), + } + } + Ok(Self { + ty: parsed_ty, + m, + dot_token, + reg_builder, + reg_builder_paren, + clock_domain, + reset, + }) + } +} + +impl HdlLetKindToTokens for HdlLetKindRegBuilder { + fn ty_to_tokens(&self, tokens: &mut TokenStream) { + if let Some((colon_token, ty)) = &self.ty { + colon_token.to_tokens(tokens); + ty.to_tokens(tokens); + } + } + + fn expr_to_tokens(&self, tokens: &mut TokenStream) { + let Self { + ty: _, + m, + dot_token, + reg_builder, + reg_builder_paren, + clock_domain, + reset, + } = self; + m.to_tokens(tokens); + dot_token.to_tokens(tokens); + reg_builder.to_tokens(tokens); + reg_builder_paren.surround(tokens, |_tokens| {}); + clock_domain.to_tokens(tokens); + reset.to_tokens(tokens); + } +} + +#[derive(Clone, Debug)] +pub(crate) struct HdlLetKindWire { + pub(crate) ty: Option<(Token![:], Box)>, + pub(crate) m: kw::m, + pub(crate) dot_token: Token![.], + pub(crate) wire: kw::wire, + pub(crate) paren: Paren, + pub(crate) ty_expr: Option>, +} + +impl_fold! { + struct HdlLetKindWire<> { + ty: Option<(Token![:], Box)>, + m: kw::m, + dot_token: Token![.], + wire: kw::wire, + paren: Paren, + ty_expr: Option>, + } +} + +impl HdlLetKindToTokens for HdlLetKindWire { + fn ty_to_tokens(&self, tokens: &mut TokenStream) { + if let Some((colon_token, ty)) = &self.ty { + colon_token.to_tokens(tokens); + ty.to_tokens(tokens); + } + } + + fn expr_to_tokens(&self, tokens: &mut TokenStream) { + let Self { + ty: _, + m, + dot_token, + wire, + paren, + ty_expr, + } = self; + m.to_tokens(tokens); + dot_token.to_tokens(tokens); + wire.to_tokens(tokens); + paren.surround(tokens, |tokens| ty_expr.to_tokens(tokens)); + } +} + +options! { + pub(crate) enum MemoryFnName { + Memory(memory), + MemoryArray(memory_array), + MemoryWithInit(memory_with_init), + } +} + +#[derive(Clone, Debug)] +pub(crate) enum MemoryFn { + Memory { + memory: kw::memory, + paren: Paren, + ty_expr: Option>, + }, + MemoryArray { + memory_array: kw::memory_array, + paren: Paren, + ty_expr: Option>, + }, + MemoryWithInit { + memory_with_init: kw::memory_with_init, + paren: Paren, + init_expr: Box, + }, +} + +impl_fold! { + enum MemoryFn<> { + Memory { + memory: kw::memory, + paren: Paren, + ty_expr: Option>, + }, + MemoryArray { + memory_array: kw::memory_array, + paren: Paren, + ty_expr: Option>, + }, + MemoryWithInit { + memory_with_init: kw::memory_with_init, + paren: Paren, + init_expr: Box, + }, + } +} + +impl MemoryFn { + fn name(&self) -> MemoryFnName { + match *self { + MemoryFn::Memory { memory, .. } => MemoryFnName::Memory((memory,)), + MemoryFn::MemoryArray { memory_array, .. } => { + MemoryFnName::MemoryArray((memory_array,)) + } + MemoryFn::MemoryWithInit { + memory_with_init, .. + } => MemoryFnName::MemoryWithInit((memory_with_init,)), + } + } + fn parse_rest(input: ParseStream, memory_fn_name: MemoryFnName) -> syn::Result { + let paren_contents; + match memory_fn_name { + MemoryFnName::Memory((memory,)) => Ok(Self::Memory { + memory, + paren: parenthesized!(paren_contents in input), + ty_expr: paren_contents.call(parse_optional_fn_arg)?, + }), + MemoryFnName::MemoryArray((memory_array,)) => Ok(Self::MemoryArray { + memory_array, + paren: parenthesized!(paren_contents in input), + ty_expr: paren_contents.call(parse_optional_fn_arg)?, + }), + MemoryFnName::MemoryWithInit((memory_with_init,)) => Ok(Self::MemoryWithInit { + memory_with_init, + paren: parenthesized!(paren_contents in input), + init_expr: paren_contents.call(parse_single_fn_arg)?, + }), + } + } +} + +impl ToTokens for MemoryFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + MemoryFn::Memory { + memory, + paren, + ty_expr, + } => { + memory.to_tokens(tokens); + paren.surround(tokens, |tokens| ty_expr.to_tokens(tokens)); + } + MemoryFn::MemoryArray { + memory_array, + paren, + ty_expr, + } => { + memory_array.to_tokens(tokens); + paren.surround(tokens, |tokens| ty_expr.to_tokens(tokens)); + } + MemoryFn::MemoryWithInit { + memory_with_init, + paren, + init_expr, + } => { + memory_with_init.to_tokens(tokens); + paren.surround(tokens, |tokens| init_expr.to_tokens(tokens)); + } + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct HdlLetKindMemory { + pub(crate) ty: Option<(Token![:], Box)>, + pub(crate) m: kw::m, + pub(crate) dot_token: Token![.], + pub(crate) memory_fn: MemoryFn, +} + +impl_fold! { + struct HdlLetKindMemory<> { + ty: Option<(Token![:], Box)>, + m: kw::m, + dot_token: Token![.], + memory_fn: MemoryFn, + } +} + +impl HdlLetKindToTokens for HdlLetKindMemory { + fn ty_to_tokens(&self, tokens: &mut TokenStream) { + if let Some((colon_token, ty)) = &self.ty { + colon_token.to_tokens(tokens); + ty.to_tokens(tokens); + } + } + + fn expr_to_tokens(&self, tokens: &mut TokenStream) { + let Self { + ty: _, + m, + dot_token, + memory_fn, + } = self; + m.to_tokens(tokens); + dot_token.to_tokens(tokens); + memory_fn.to_tokens(tokens); + } +} + +impl HdlLetKindMemory { + fn rest_of_parse( + input: ParseStream, + parsed_ty: Option<(Token![:], Box)>, + _after_ty: Token![=], + m: kw::m, + dot_token: Token![.], + memory_fn_name: MemoryFnName, + ) -> syn::Result { + Ok(Self { + ty: parsed_ty, + m, + dot_token, + memory_fn: MemoryFn::parse_rest(input, memory_fn_name)?, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum HdlLetKind { + IO(HdlLetKindIO), + Instance(HdlLetKindInstance), + RegBuilder(HdlLetKindRegBuilder), + Wire(HdlLetKindWire), + Memory(HdlLetKindMemory), +} + +impl_fold! { + enum HdlLetKind<> { + IO(HdlLetKindIO), + Instance(HdlLetKindInstance), + RegBuilder(HdlLetKindRegBuilder), + Wire(HdlLetKindWire), + Memory(HdlLetKindMemory), + } +} + +fn parsed_ty_or_err( + parsed_ty: Option<(Token![:], Box)>, + after_ty: Token![=], +) -> syn::Result<(Token![:], Box)> { + if let Some(retval) = parsed_ty { + Ok(retval) + } else { + Err(Error::new_spanned(after_ty, "missing `:`")) + } +} + +impl HdlLetKindIO { + fn rest_of_parse( + input: ParseStream, + parsed_ty: Option<(Token![:], Box)>, + after_ty: Token![=], + m: kw::m, + dot_token: Token![.], + kind: ModuleIOKind, + ) -> syn::Result { + let (colon_token, ty) = parsed_ty_or_err(parsed_ty, after_ty)?; + let paren_contents; + Ok(Self { + colon_token, + ty, + m, + dot_token, + kind, + paren: parenthesized!(paren_contents in input), + ty_expr: paren_contents.call(parse_optional_fn_arg)?, + }) + } +} + +impl HdlLetKindParse for HdlLetKind { + type ParsedTy = Option<(Token![:], Box)>; + + fn parse_ty(input: ParseStream) -> syn::Result { + let ty_lookahead = input.lookahead1(); + if ty_lookahead.peek(Token![:]) { + Ok(Some((input.parse()?, input.parse()?))) + } else if ty_lookahead.peek(Token![=]) { + Ok(None) + } else { + Err(ty_lookahead.error()) + } + } + + fn parse_expr( + _name: &Ident, + parsed_ty: Self::ParsedTy, + after_ty: Token![=], + input: ParseStream, + ) -> syn::Result { + let m = input.parse()?; + let dot_token = input.parse()?; + let kind: LetFnKind = input.parse()?; + match kind { + LetFnKind::Input(input_token) => HdlLetKindIO::rest_of_parse( + input, + parsed_ty, + after_ty, + m, + dot_token, + ModuleIOKind::Input(input_token), + ) + .map(Self::IO), + LetFnKind::Output(output) => HdlLetKindIO::rest_of_parse( + input, + parsed_ty, + after_ty, + m, + dot_token, + ModuleIOKind::Output(output), + ) + .map(Self::IO), + LetFnKind::Instance((instance,)) => { + if let Some(parsed_ty) = parsed_ty { + return Err(Error::new_spanned( + parsed_ty.1, + "type annotation not allowed for instance", + )); + } + let paren_contents; + Ok(Self::Instance(HdlLetKindInstance { + m, + dot_token, + instance, + paren: parenthesized!(paren_contents in input), + module: paren_contents.call(parse_single_fn_arg)?, + })) + } + LetFnKind::RegBuilder((reg_builder,)) => HdlLetKindRegBuilder::rest_of_parse( + input, + parsed_ty, + after_ty, + m, + dot_token, + reg_builder, + ) + .map(Self::RegBuilder), + LetFnKind::Wire((wire,)) => { + let paren_contents; + Ok(Self::Wire(HdlLetKindWire { + ty: parsed_ty, + m, + dot_token, + wire, + paren: parenthesized!(paren_contents in input), + ty_expr: paren_contents.call(parse_optional_fn_arg)?, + })) + } + LetFnKind::Memory(fn_name) => HdlLetKindMemory::rest_of_parse( + input, + parsed_ty, + after_ty, + m, + dot_token, + MemoryFnName::Memory(fn_name), + ) + .map(Self::Memory), + LetFnKind::MemoryArray(fn_name) => HdlLetKindMemory::rest_of_parse( + input, + parsed_ty, + after_ty, + m, + dot_token, + MemoryFnName::MemoryArray(fn_name), + ) + .map(Self::Memory), + LetFnKind::MemoryWithInit(fn_name) => HdlLetKindMemory::rest_of_parse( + input, + parsed_ty, + after_ty, + m, + dot_token, + MemoryFnName::MemoryWithInit(fn_name), + ) + .map(Self::Memory), + } + } +} + +impl HdlLetKindToTokens for HdlLetKind { + fn ty_to_tokens(&self, tokens: &mut TokenStream) { + match self { + HdlLetKind::IO(v) => v.ty_to_tokens(tokens), + HdlLetKind::Instance(v) => v.ty_to_tokens(tokens), + HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens), + HdlLetKind::Wire(v) => v.ty_to_tokens(tokens), + HdlLetKind::Memory(v) => v.ty_to_tokens(tokens), + } + } + + fn expr_to_tokens(&self, tokens: &mut TokenStream) { + match self { + HdlLetKind::IO(v) => v.expr_to_tokens(tokens), + HdlLetKind::Instance(v) => v.expr_to_tokens(tokens), + HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens), + HdlLetKind::Wire(v) => v.expr_to_tokens(tokens), + HdlLetKind::Memory(v) => v.expr_to_tokens(tokens), + } + } +} + +with_debug_clone_and_fold! { + #[allow(dead_code)] + pub(crate) struct HdlLet { + pub(crate) attrs: Vec, + pub(crate) hdl_attr: HdlAttr, + pub(crate) let_token: Token![let], + pub(crate) mut_token: Option, + pub(crate) name: Ident, + pub(crate) eq_token: Token![=], + pub(crate) kind: Kind, + pub(crate) semi_token: Token![;], + } +} + +impl HdlLet { + pub(crate) fn try_map( + self, + f: impl FnOnce(Kind) -> Result, + ) -> Result, E> { + let Self { + attrs, + hdl_attr, + let_token, + mut_token, + name, + eq_token, + kind, + semi_token, + } = self; + let kind = f(kind)?; + Ok(HdlLet { + attrs, + hdl_attr, + let_token, + mut_token, + name, + eq_token, + kind, + semi_token, + }) + } + pub(crate) fn map(self, f: impl FnOnce(Kind) -> Kind2) -> HdlLet { + match self.try_map(|kind| Ok::(f(kind))) { + Ok(v) => v, + Err(e) => match e {}, + } + } +} + +pub trait HdlLetKindParse: Sized { + type ParsedTy; + fn parse_ty(input: ParseStream) -> syn::Result; + fn parse_expr( + name: &Ident, + parsed_ty: Self::ParsedTy, + after_ty: Token![=], + input: ParseStream, + ) -> syn::Result; +} + +pub trait HdlLetKindToTokens { + fn ty_to_tokens(&self, tokens: &mut TokenStream); + fn expr_to_tokens(&self, tokens: &mut TokenStream); +} + +impl Parse for HdlLet { + fn parse(input: ParseStream) -> syn::Result { + let mut attrs = Attribute::parse_outer(input)?; + let hdl_attr = HdlAttr::parse_and_take_attr(&mut attrs)?; + let let_token = input.parse()?; + let mut_token = input.parse()?; + let hdl_attr = hdl_attr.ok_or_else(|| Error::new_spanned(let_token, "missing #[hdl]"))?; + let name = input.parse()?; + check_name_conflicts_with_module_builder(&name)?; + let parsed_ty = Kind::parse_ty(input)?; + let eq_token = input.parse()?; + let kind = Kind::parse_expr(&name, parsed_ty, eq_token, input)?; + let retval = Self { + attrs, + hdl_attr, + let_token, + mut_token, + name, + eq_token, + kind, + semi_token: input.parse()?, + }; + Ok(retval) + } +} + +impl ToTokens for HdlLet { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + hdl_attr, + let_token, + mut_token, + name, + eq_token, + kind, + semi_token, + } = self; + for attr in attrs { + attr.to_tokens(tokens); + } + hdl_attr.to_tokens(tokens); + let_token.to_tokens(tokens); + mut_token.to_tokens(tokens); + name.to_tokens(tokens); + kind.ty_to_tokens(tokens); + eq_token.to_tokens(tokens); + kind.expr_to_tokens(tokens); + semi_token.to_tokens(tokens); + } +} + +fn parse_quote_let_pat>( + mut_token: &Option, + name: &Ident, + ty: Option<(C, T)>, + map_ty: impl FnOnce(T) -> R, +) -> Pat { + match ty { + Some((colon_token, ty)) => { + let colon_token = colon_token.borrow(); + let ty = map_ty(ty); + Pat::Type(parse_quote! { #mut_token #name #colon_token #ty }) + } + None => parse_quote! { #mut_token #name }, + } +} + +fn wrap_ty_with_expr(ty: impl ToTokens) -> Type { + parse_quote_spanned! {ty.span()=> + ::fayalite::expr::Expr<#ty> + } +} + +fn unwrap_or_fixed_type(expr: Option, span: Span) -> TokenStream { + expr.map(ToTokens::into_token_stream).unwrap_or_else(|| { + quote_spanned! {span=> + ::fayalite::ty::FixedType::fixed_type() + } + }) +} + +struct ImplicitName { + name: T, + span: Span, +} + +impl ToTokens for ImplicitName { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = LitStr::new(&self.name.to_string(), self.span); + quote_spanned! {self.span=> + ::fayalite::module::ImplicitName(#name) + } + .to_tokens(tokens); + } +} + +struct Visitor { + module_kind: ModuleKind, + errors: Errors, + io: Vec, + block_depth: usize, +} + +impl Visitor { + fn take_hdl_attr(&mut self, attrs: &mut Vec) -> Option> { + self.errors.unwrap_or( + HdlAttr::parse_and_take_attr(attrs), + Some(syn::parse2::(quote! {}).unwrap().into()), + ) + } + fn require_normal_module(&mut self, spanned: impl ToTokens) { + match self.module_kind { + ModuleKind::Extern => { + self.errors + .error(spanned, "not allowed in #[hdl_module(extern)]"); + } + ModuleKind::Normal => {} + } + } + fn process_hdl_if(&mut self, hdl_attr: HdlAttr, expr_if: ExprIf) -> Expr { + let ExprIf { + attrs, + if_token, + cond, + then_branch, + else_branch, + } = expr_if; + self.require_normal_module(if_token); + let else_expr = else_branch.unzip().1.map(|else_expr| match *else_expr { + Expr::If(expr_if) => self.process_hdl_if(hdl_attr.clone(), expr_if), + expr => expr, + }); + if let Expr::Let(ExprLet { + attrs: let_attrs, + let_token: _, + pat, + eq_token: _, + expr, + }) = *cond + { + let else_expr = else_expr.unwrap_or_else(|| parse_quote_spanned! {if_token.span=> {}}); + return self.process_hdl_match( + hdl_attr, + parse_quote_spanned! {if_token.span=> + #(#attrs)* + match #expr { + #(#let_attrs)* #pat => #then_branch, + _ => #else_expr, + } + }, + ); + } + if let Some(else_expr) = else_expr { + parse_quote_spanned! {if_token.span=> + #(#attrs)* + { + let mut __scope = m.if_(#cond); + let _: () = #then_branch; + let mut __scope = __scope.else_(); + let _: () = #else_expr; + } + } + } else { + parse_quote_spanned! {if_token.span=> + #(#attrs)* + { + let mut __scope = m.if_(#cond); + let _: () = #then_branch; + } + } + } + } + fn process_hdl_let_io(&mut self, hdl_let: HdlLet) -> Local { + let name = &hdl_let.name; + let colon_token = hdl_let.kind.colon_token; + let ty = &hdl_let.kind.ty; + let m = hdl_let.kind.m; + let dot = hdl_let.kind.dot_token; + let kind = hdl_let.kind.kind; + let ty_expr = unwrap_or_fixed_type(hdl_let.kind.ty_expr.as_ref(), kind.span()); + let mut expr = quote! {#m #dot #kind}; + hdl_let.kind.paren.surround(&mut expr, |expr| { + let name_str = ImplicitName { + name, + span: name.span(), + }; + quote_spanned! {name.span()=> + #name_str, #ty_expr + } + .to_tokens(expr); + }); + let mut attrs = hdl_let.attrs.clone(); + match self.module_kind { + ModuleKind::Extern => attrs.push(parse_quote_spanned! {hdl_let.let_token.span=> + #[allow(unused_variables)] + }), + ModuleKind::Normal => {} + } + let let_stmt = Local { + attrs, + let_token: hdl_let.let_token, + pat: parse_quote_let_pat( + &hdl_let.mut_token, + name, + Some((colon_token, &ty)), + wrap_ty_with_expr, + ), + init: Some(LocalInit { + eq_token: hdl_let.eq_token, + expr: parse_quote! { #expr }, + diverge: None, + }), + semi_token: hdl_let.semi_token, + }; + self.io.push(hdl_let); + let_stmt + } + fn process_hdl_let_instance(&mut self, hdl_let: HdlLet) -> Local { + let HdlLet { + attrs, + hdl_attr: _, + let_token, + mut_token, + name, + eq_token, + kind: + HdlLetKindInstance { + m, + dot_token, + instance, + paren, + module, + }, + semi_token, + } = hdl_let; + self.require_normal_module(instance); + let mut expr = quote! {#m #dot_token #instance}; + paren.surround(&mut expr, |expr| { + let name_str = ImplicitName { + name: &name, + span: name.span(), + }; + quote_spanned! {name.span()=> + #name_str, #module + } + .to_tokens(expr); + }); + Local { + attrs, + let_token, + pat: parse_quote! { #mut_token #name }, + init: Some(LocalInit { + eq_token, + expr: parse_quote! { #expr }, + diverge: None, + }), + semi_token, + } + } + fn process_hdl_let_reg_builder(&mut self, hdl_let: HdlLet) -> Local { + let name = &hdl_let.name; + let m = hdl_let.kind.m; + let dot = hdl_let.kind.dot_token; + let reg_builder = hdl_let.kind.reg_builder; + self.require_normal_module(reg_builder); + let mut expr = quote! {#m #dot #reg_builder}; + hdl_let.kind.reg_builder_paren.surround(&mut expr, |expr| { + let name_str = ImplicitName { + name, + span: name.span(), + }; + quote_spanned! {name.span()=> + #name_str + } + .to_tokens(expr); + }); + hdl_let.kind.clock_domain.to_tokens(&mut expr); + match hdl_let.kind.reset { + RegBuilderReset::NoReset { + dot_token, + no_reset, + paren, + ty_expr, + } => { + let ty_expr = unwrap_or_fixed_type(ty_expr.as_ref(), reg_builder.span()); + dot_token.to_tokens(&mut expr); + no_reset.to_tokens(&mut expr); + paren.surround(&mut expr, |expr| ty_expr.to_tokens(expr)); + } + RegBuilderReset::Reset { .. } | RegBuilderReset::ResetDefault { .. } => { + hdl_let.kind.reset.to_tokens(&mut expr); + } + } + Local { + attrs: hdl_let.attrs.clone(), + let_token: hdl_let.let_token, + pat: parse_quote_let_pat( + &hdl_let.mut_token, + name, + hdl_let.kind.ty.clone(), + wrap_ty_with_expr, + ), + init: Some(LocalInit { + eq_token: hdl_let.eq_token, + expr: parse_quote_spanned! {reg_builder.span()=> + #expr.build() + }, + diverge: None, + }), + semi_token: hdl_let.semi_token, + } + } + fn process_hdl_let_wire(&mut self, hdl_let: HdlLet) -> Local { + let name = &hdl_let.name; + let m = hdl_let.kind.m; + let dot = hdl_let.kind.dot_token; + let wire = hdl_let.kind.wire; + self.require_normal_module(wire); + let ty_expr = unwrap_or_fixed_type(hdl_let.kind.ty_expr.as_ref(), wire.span()); + let mut expr = quote! {#m #dot #wire}; + hdl_let.kind.paren.surround(&mut expr, |expr| { + let name_str = ImplicitName { + name, + span: name.span(), + }; + quote_spanned! {name.span()=> + #name_str, #ty_expr + } + .to_tokens(expr); + }); + Local { + attrs: hdl_let.attrs.clone(), + let_token: hdl_let.let_token, + pat: parse_quote_let_pat( + &hdl_let.mut_token, + name, + hdl_let.kind.ty.clone(), + wrap_ty_with_expr, + ), + init: Some(LocalInit { + eq_token: hdl_let.eq_token, + expr: parse_quote! { #expr }, + diverge: None, + }), + semi_token: hdl_let.semi_token, + } + } + fn process_hdl_let_memory(&mut self, hdl_let: HdlLet) -> Local { + let name = &hdl_let.name; + let m = hdl_let.kind.m; + let dot = hdl_let.kind.dot_token; + let memory_fn = hdl_let.kind.memory_fn; + let memory_fn_name = memory_fn.name(); + self.require_normal_module(memory_fn_name); + let mut expr = quote! {#m #dot #memory_fn_name}; + let (paren, arg) = match memory_fn { + MemoryFn::Memory { + memory, + paren, + ty_expr, + } => (paren, unwrap_or_fixed_type(ty_expr.as_ref(), memory.span())), + MemoryFn::MemoryArray { + memory_array, + paren, + ty_expr, + } => ( + paren, + unwrap_or_fixed_type(ty_expr.as_ref(), memory_array.span()), + ), + MemoryFn::MemoryWithInit { + memory_with_init: _, + paren, + init_expr, + } => (paren, quote! { #init_expr }), + }; + paren.surround(&mut expr, |expr| { + let name_str = ImplicitName { + name, + span: name.span(), + }; + quote_spanned! {name.span()=> + #name_str, + } + .to_tokens(expr); + arg.to_tokens(expr); + }); + Local { + attrs: hdl_let.attrs.clone(), + let_token: hdl_let.let_token, + pat: parse_quote_let_pat(&hdl_let.mut_token, name, hdl_let.kind.ty.clone(), |ty| ty), + init: Some(LocalInit { + eq_token: hdl_let.eq_token, + expr: parse_quote! { #expr }, + diverge: None, + }), + semi_token: hdl_let.semi_token, + } + } + fn process_hdl_let(&mut self, hdl_let: HdlLet) -> Local { + macro_rules! the_match { + ($($Variant:ident => $fn:ident,)*) => { + match hdl_let.kind { + $( + HdlLetKind::$Variant(_) => { + self.$fn(hdl_let.map(|kind| match kind { + HdlLetKind::$Variant(kind) => kind, + _ => unreachable!(), + })) + } + )* + } + }; + } + the_match! { + IO => process_hdl_let_io, + Instance => process_hdl_let_instance, + RegBuilder => process_hdl_let_reg_builder, + Wire => process_hdl_let_wire, + Memory => process_hdl_let_memory, + } + } + fn process_int_literal( + &mut self, + span: Span, + base10_digits: &str, + suffix: &str, + ) -> Option { + let Some(("hdl", suffix)) = suffix.split_once('_') else { + return None; + }; + let signed = match suffix.as_bytes().first() { + Some(b'u') => false, + Some(b'i') => true, + _ => { + self.errors.push(Error::new( + span, + "invalid hdl integer suffix -- use something like 123_hdl_u5 or -345_hdl_i20", + )); + return None; + } + }; + let width: usize = self + .errors + .ok(suffix[1..].parse().map_err(|e| Error::new(span, e)))?; + let value: BigInt = self + .errors + .ok(base10_digits.parse().map_err(|e| Error::new(span, e)))?; + let (negative, bytes) = match value.sign() { + Sign::Minus => (true, value.magnitude().to_bytes_le()), + Sign::NoSign => (false, vec![]), + Sign::Plus => (false, value.magnitude().to_bytes_le()), + }; + Some(parse_quote_spanned! {span=> + ::fayalite::int::make_int_literal::<#signed, #width>(#negative, &[#(#bytes,)*]).to_int_expr() + }) + } + fn process_literal(&mut self, literal: ExprLit) -> Expr { + let ExprLit { attrs, lit } = literal; + match &lit { + Lit::Byte(lit_byte) if lit_byte.suffix() == "hdl" => { + if let Some(retval) = self.process_int_literal( + lit_byte.span(), + &lit_byte.value().to_string(), + "hdl_u8", + ) { + return retval; + } + } + Lit::Int(lit_int) => { + if let Some(retval) = self.process_int_literal( + lit_int.span(), + lit_int.base10_digits(), + lit_int.suffix(), + ) { + return retval; + } + } + _ => {} + } + fold_expr_lit(self, ExprLit { attrs, lit }).into() + } + fn process_unary(&mut self, unary: ExprUnary) -> Expr { + if let UnOp::Neg(minus_sign) = unary.op { + if let Expr::Lit(ExprLit { + attrs: _, + lit: Lit::Int(lit_int), + }) = &*unary.expr + { + let span = lit_int.span(); + let span = minus_sign.span.join(span).unwrap_or(span); + if let Some(retval) = self.process_int_literal( + span, + &format!("-{}", lit_int.base10_digits()), + lit_int.suffix(), + ) { + return retval; + } + } + } + fold_expr_unary(self, unary).into() + } +} + +fn empty_let() -> Local { + Local { + attrs: vec![], + let_token: Default::default(), + pat: Pat::Type(parse_quote! {_: ()}), + init: None, + semi_token: Default::default(), + } +} + +impl Fold for Visitor { + fn fold_item(&mut self, item: Item) -> Item { + // don't process item interiors + item + } + + fn fold_generic_argument(&mut self, i: GenericArgument) -> GenericArgument { + // don't process inside generic arguments + i + } + + fn fold_attribute(&mut self, attr: Attribute) -> Attribute { + if is_hdl_attr(&attr) { + self.errors + .error(&attr, "#[hdl] attribute not supported here"); + } + attr + } + + fn fold_expr_repeat(&mut self, i: ExprRepeat) -> ExprRepeat { + let ExprRepeat { + mut attrs, + bracket_token, + mut expr, + semi_token, + len, + } = i; + attrs = attrs + .into_iter() + .map(|attr| self.fold_attribute(attr)) + .collect(); + *expr = self.fold_expr(*expr); + // don't process inside len, since it's a const context + ExprRepeat { + attrs, + bracket_token, + expr, + semi_token, + len, + } + } + + fn fold_expr(&mut self, expr: Expr) -> Expr { + macro_rules! match_hdl_expr { + ( + match $expr:ident { + $($Variant:ident => $process_fn:ident,)* + } + ) => { + match $expr { + $(Expr::$Variant(mut expr) => { + if let Some(hdl_attr) = self.take_hdl_attr(&mut expr.attrs) { + let expr = expr.do_fold(self); + self.$process_fn(hdl_attr, expr) + } else { + expr.do_fold(self).into() + } + })* + Expr::Lit(literal) => self.process_literal(literal), + Expr::Unary(unary) => self.process_unary(unary), + expr => fold_expr(self, expr), + } + }; + } + match_hdl_expr! { + match expr { + If => process_hdl_if, + Match => process_hdl_match, + Array => process_hdl_array, + Struct => process_hdl_struct, + Tuple => process_hdl_tuple, + Call => process_hdl_call, + Path => process_hdl_path, + } + } + } + + fn fold_local(&mut self, let_stmt: Local) -> Local { + match self + .errors + .ok(HdlAttr::::parse_and_leave_attr(&let_stmt.attrs)) + { + None => return empty_let(), + Some(None) => return fold_local(self, let_stmt), + Some(Some(HdlAttr { .. })) => {} + }; + let hdl_let = syn::parse2::(let_stmt.into_token_stream()); + let Some(hdl_let) = self.errors.ok(hdl_let) else { + return empty_let(); + }; + let hdl_let = hdl_let.do_fold(self); + self.process_hdl_let(hdl_let) + } + + fn fold_stmt(&mut self, stmt: syn::Stmt) -> syn::Stmt { + match stmt { + syn::Stmt::Item(_) => stmt, // don't process inside items + syn::Stmt::Macro(_) => stmt, // don't process inside macros + syn::Stmt::Local(_) | syn::Stmt::Expr(_, _) => fold_stmt(self, stmt), + } + } + + fn fold_block(&mut self, i: syn::Block) -> syn::Block { + self.block_depth += 1; + let retval = syn::fold::fold_block(self, i); + self.block_depth -= 1; + retval + } +} + +pub(crate) fn transform_body( + module_kind: ModuleKind, + mut body: Box, +) -> syn::Result<(Box, Vec)> { + let mut visitor = Visitor { + module_kind, + errors: Errors::new(), + io: vec![], + block_depth: 0, + }; + *body = syn::fold::fold_block(&mut visitor, *body); + visitor.errors.finish()?; + Ok((body, visitor.io)) +} diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs new file mode 100644 index 0000000..9deb07b --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{module::transform_body::Visitor, options, Errors, HdlAttr}; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt}; +use syn::{ + parse::Nothing, + parse_quote, parse_quote_spanned, + punctuated::{Pair, Punctuated}, + spanned::Spanned, + token::{Brace, Paren}, + Attribute, Expr, ExprArray, ExprCall, ExprGroup, ExprPath, ExprStruct, ExprTuple, FieldValue, + Ident, Index, Member, Path, PathArguments, PathSegment, Token, TypePath, +}; + +options! { + #[options = AggregateLiteralOptions] + #[no_ident_fragment] + pub(crate) enum AggregateLiteralOption { + Struct(struct_), + Enum(enum_), + } +} + +#[derive(Clone, Debug)] +pub(crate) struct StructOrEnumPath { + pub(crate) ty: TypePath, + pub(crate) variant: Option<(TypePath, Ident)>, +} + +#[derive(Debug, Copy, Clone)] +pub(crate) struct SingleSegmentVariant { + pub(crate) name: &'static str, + pub(crate) make_type_path: fn(Span, &PathArguments) -> Path, +} + +impl StructOrEnumPath { + pub(crate) const SINGLE_SEGMENT_VARIANTS: &'static [SingleSegmentVariant] = { + fn make_option_type_path(span: Span, arguments: &PathArguments) -> Path { + let arguments = if arguments.is_none() { + quote_spanned! {span=> + <_> + } + } else { + arguments.to_token_stream() + }; + parse_quote_spanned! {span=> + ::fayalite::__std::option::Option #arguments + } + } + fn make_result_type_path(span: Span, arguments: &PathArguments) -> Path { + let arguments = if arguments.is_none() { + quote_spanned! {span=> + <_, _> + } + } else { + arguments.to_token_stream() + }; + parse_quote_spanned! {span=> + ::fayalite::__std::result::Result #arguments + } + } + &[ + SingleSegmentVariant { + name: "Some", + make_type_path: make_option_type_path, + }, + SingleSegmentVariant { + name: "None", + make_type_path: make_option_type_path, + }, + SingleSegmentVariant { + name: "Ok", + make_type_path: make_result_type_path, + }, + SingleSegmentVariant { + name: "Err", + make_type_path: make_result_type_path, + }, + ] + }; + pub(crate) fn new( + errors: &mut Errors, + path: TypePath, + options: &AggregateLiteralOptions, + ) -> Result { + let Path { + leading_colon, + segments, + } = &path.path; + let qself_position = path.qself.as_ref().map(|qself| qself.position).unwrap_or(0); + let variant_name = if qself_position < segments.len() { + Some(segments.last().unwrap().ident.clone()) + } else { + None + }; + let enum_type = 'guess_enum_type: { + if options.enum_.is_some() { + if let Some((struct_,)) = options.struct_ { + errors.error( + struct_, + "can't specify both #[hdl(enum)] and #[hdl(struct)]", + ); + } + break 'guess_enum_type Some(None); + } + if options.struct_.is_some() { + break 'guess_enum_type None; + } + if path.qself.is_none() && leading_colon.is_none() && segments.len() == 1 { + let PathSegment { ident, arguments } = &segments[0]; + for &SingleSegmentVariant { + name, + make_type_path, + } in Self::SINGLE_SEGMENT_VARIANTS + { + if ident == name { + break 'guess_enum_type Some(Some(TypePath { + qself: None, + path: make_type_path(ident.span(), arguments), + })); + } + } + } + if segments.len() == qself_position + 2 + && segments[qself_position + 1].arguments.is_none() + && (path.qself.is_some() + || segments[qself_position].ident.to_string().as_bytes()[0] + .is_ascii_uppercase()) + { + let mut ty = path.clone(); + ty.path.segments.pop(); + ty.path.segments.pop_punct(); + break 'guess_enum_type Some(Some(ty)); + } + None + }; + if let Some(enum_type) = enum_type { + let ty = if let Some(enum_type) = enum_type { + enum_type + } else { + if qself_position >= segments.len() { + errors.error(path, "#[hdl]: can't figure out enum's type"); + return Err(()); + } + let mut ty = path.clone(); + ty.path.segments.pop(); + ty.path.segments.pop_punct(); + ty + }; + let Some(variant_name) = variant_name else { + errors.error(path, "#[hdl]: can't figure out enum's variant name"); + return Err(()); + }; + Ok(Self { + ty, + variant: Some((path, variant_name)), + }) + } else { + Ok(Self { + ty: path, + variant: None, + }) + } + } +} + +#[derive(Copy, Clone, Debug)] +pub(crate) enum BraceOrParen { + Brace(Brace), + Paren(Paren), +} + +impl BraceOrParen { + pub(crate) fn surround(self, tokens: &mut TokenStream, f: impl FnOnce(&mut TokenStream)) { + match self { + BraceOrParen::Brace(v) => v.surround(tokens, f), + BraceOrParen::Paren(v) => v.surround(tokens, f), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct StructOrEnumLiteralField { + pub(crate) attrs: Vec, + pub(crate) member: Member, + pub(crate) colon_token: Option, + pub(crate) expr: Expr, +} + +#[derive(Debug, Clone)] +pub(crate) struct StructOrEnumLiteral { + pub(crate) attrs: Vec, + pub(crate) path: TypePath, + pub(crate) brace_or_paren: BraceOrParen, + pub(crate) fields: Punctuated, + pub(crate) dot2_token: Option, + pub(crate) rest: Option>, +} + +impl StructOrEnumLiteral { + pub(crate) fn map_field_exprs(self, mut f: impl FnMut(Expr) -> Expr) -> Self { + self.map_fields(|mut field| { + field.expr = f(field.expr); + field + }) + } + pub(crate) fn map_fields( + self, + mut f: impl FnMut(StructOrEnumLiteralField) -> StructOrEnumLiteralField, + ) -> Self { + let Self { + attrs, + path, + brace_or_paren, + fields, + dot2_token, + rest, + } = self; + let fields = Punctuated::from_iter(fields.into_pairs().map(|p| { + let (field, comma) = p.into_tuple(); + Pair::new(f(field), comma) + })); + Self { + attrs, + path, + brace_or_paren, + fields, + dot2_token, + rest, + } + } +} + +impl From for StructOrEnumLiteral { + fn from(value: ExprStruct) -> Self { + let ExprStruct { + attrs, + qself, + path, + brace_token, + fields, + dot2_token, + rest, + } = value; + Self { + attrs, + path: TypePath { qself, path }, + brace_or_paren: BraceOrParen::Brace(brace_token), + fields: Punctuated::from_iter(fields.into_pairs().map(|v| { + let ( + FieldValue { + attrs, + member, + colon_token, + expr, + }, + comma, + ) = v.into_tuple(); + Pair::new( + StructOrEnumLiteralField { + attrs, + member, + colon_token, + expr, + }, + comma, + ) + })), + dot2_token, + rest, + } + } +} + +fn expr_to_member(expr: &Expr) -> Option { + syn::parse2(expr.to_token_stream()).ok() +} + +impl ToTokens for StructOrEnumLiteral { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + path, + brace_or_paren, + fields, + dot2_token, + rest, + } = self; + tokens.append_all(attrs); + path.to_tokens(tokens); + brace_or_paren.surround(tokens, |tokens| { + match brace_or_paren { + BraceOrParen::Brace(_) => { + for ( + StructOrEnumLiteralField { + attrs, + member, + mut colon_token, + expr, + }, + comma, + ) in fields.pairs().map(|v| v.into_tuple()) + { + tokens.append_all(attrs); + if Some(member) != expr_to_member(expr).as_ref() { + colon_token = Some(::default()); + } + member.to_tokens(tokens); + colon_token.to_tokens(tokens); + expr.to_tokens(tokens); + comma.to_tokens(tokens); + } + } + BraceOrParen::Paren(_) => { + for ( + StructOrEnumLiteralField { + attrs, + member: _, + colon_token: _, + expr, + }, + comma, + ) in fields.pairs().map(|v| v.into_tuple()) + { + tokens.append_all(attrs); + expr.to_tokens(tokens); + comma.to_tokens(tokens); + } + } + } + if let Some(rest) = rest { + dot2_token.unwrap_or_default().to_tokens(tokens); + rest.to_tokens(tokens); + } + }); + } +} + +impl Visitor { + pub(crate) fn process_hdl_array( + &mut self, + hdl_attr: HdlAttr, + mut expr_array: ExprArray, + ) -> Expr { + self.require_normal_module(hdl_attr); + for elem in &mut expr_array.elems { + *elem = parse_quote_spanned! {elem.span()=> + ::fayalite::expr::ToExpr::to_expr(&(#elem)) + }; + } + parse_quote! {::fayalite::expr::ToExpr::to_expr(&#expr_array)} + } + pub(crate) fn process_struct_enum( + &mut self, + hdl_attr: HdlAttr, + mut literal: StructOrEnumLiteral, + ) -> Expr { + let span = hdl_attr.hdl.span; + if let Some(rest) = literal.rest.take() { + self.errors + .error(rest, "#[hdl] struct functional update syntax not supported"); + } + let mut next_var = 0usize; + let mut new_var = || -> Ident { + let retval = format_ident!("__v{}", next_var, span = span); + next_var += 1; + retval + }; + let infallible_var = new_var(); + let retval_var = new_var(); + let mut lets = vec![]; + let mut build_steps = vec![]; + let literal = literal.map_field_exprs(|expr| { + let field_var = new_var(); + lets.push(quote_spanned! {span=> + let #field_var = ::fayalite::expr::ToExpr::to_expr(&#expr); + }); + parse_quote! { #field_var } + }); + let Ok(StructOrEnumPath { ty, variant }) = + StructOrEnumPath::new(&mut self.errors, literal.path.clone(), &hdl_attr.body) + else { + return parse_quote_spanned! {span=> + {} + }; + }; + for StructOrEnumLiteralField { + attrs: _, + member, + colon_token: _, + expr, + } in literal.fields.iter() + { + let field_fn = format_ident!("field_{}", member); + build_steps.push(quote_spanned! {span=> + let #retval_var = #retval_var.#field_fn(#expr); + }); + } + let check_literal = literal.map_field_exprs(|expr| { + parse_quote_spanned! {span=> + ::fayalite::expr::value_from_expr_type(#expr, #infallible_var) + } + }); + let make_expr_fn = if let Some((_variant_path, variant_ident)) = &variant { + let variant_fn = format_ident!("variant_{}", variant_ident); + build_steps.push(quote_spanned! {span=> + let #retval_var = #retval_var.#variant_fn(); + }); + quote_spanned! {span=> + ::fayalite::expr::make_enum_expr + } + } else { + build_steps.push(quote_spanned! {span=> + let #retval_var = #retval_var.build(); + }); + quote_spanned! {span=> + ::fayalite::expr::make_bundle_expr + } + }; + let variant_or_type = + variant.map_or_else(|| ty.clone(), |(variant_path, _variant_ident)| variant_path); + parse_quote_spanned! {span=> + { + #(#lets)* + #make_expr_fn::<#ty>(|#infallible_var| { + let #retval_var = #check_literal; + match #retval_var { + #variant_or_type { .. } => #retval_var, + #[allow(unreachable_patterns)] + _ => match #infallible_var {}, + } + }, |#retval_var| { + #(#build_steps)* + #retval_var + }) + } + } + } + pub(crate) fn process_hdl_struct( + &mut self, + hdl_attr: HdlAttr, + expr_struct: ExprStruct, + ) -> Expr { + self.require_normal_module(&hdl_attr); + self.process_struct_enum(hdl_attr, expr_struct.into()) + } + pub(crate) fn process_hdl_tuple( + &mut self, + hdl_attr: HdlAttr, + expr_tuple: ExprTuple, + ) -> Expr { + self.require_normal_module(hdl_attr); + parse_quote_spanned! {expr_tuple.span()=> + ::fayalite::expr::ToExpr::to_expr(&#expr_tuple) + } + } + pub(crate) fn process_hdl_path( + &mut self, + hdl_attr: HdlAttr, + expr_path: ExprPath, + ) -> Expr { + self.require_normal_module(hdl_attr); + parse_quote_spanned! {expr_path.span()=> + ::fayalite::expr::ToExpr::to_expr(&#expr_path) + } + } + pub(crate) fn process_hdl_call( + &mut self, + hdl_attr: HdlAttr, + expr_call: ExprCall, + ) -> Expr { + self.require_normal_module(&hdl_attr); + let ExprCall { + attrs: mut literal_attrs, + func, + paren_token, + args, + } = expr_call; + let mut path_expr = *func; + let path = loop { + break match path_expr { + Expr::Group(ExprGroup { + attrs, + group_token: _, + expr, + }) => { + literal_attrs.extend(attrs); + path_expr = *expr; + continue; + } + Expr::Path(ExprPath { attrs, qself, path }) => { + literal_attrs.extend(attrs); + TypePath { qself, path } + } + _ => { + self.errors.error(&path_expr, "missing tuple struct's name"); + return parse_quote_spanned! {path_expr.span()=> + {} + }; + } + }; + }; + let fields = Punctuated::from_iter(args.into_pairs().enumerate().map(|(index, p)| { + let (expr, comma) = p.into_tuple(); + let mut index = Index::from(index); + index.span = hdl_attr.hdl.span; + Pair::new( + StructOrEnumLiteralField { + attrs: vec![], + member: Member::Unnamed(index), + colon_token: None, + expr, + }, + comma, + ) + })); + self.process_struct_enum( + hdl_attr, + StructOrEnumLiteral { + attrs: literal_attrs, + path, + brace_or_paren: BraceOrParen::Paren(paren_token), + fields, + dot2_token: None, + rest: None, + }, + ) + } +} diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs new file mode 100644 index 0000000..b4d8ad2 --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + fold::impl_fold, + module::transform_body::{ + expand_aggregate_literals::{AggregateLiteralOptions, StructOrEnumPath}, + with_debug_clone_and_fold, Visitor, + }, + Errors, HdlAttr, +}; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, TokenStreamExt}; +use syn::{ + fold::{fold_arm, fold_expr_match, fold_pat, Fold}, + parse::Nothing, + parse_quote_spanned, + punctuated::{Pair, Punctuated}, + spanned::Spanned, + token::{Brace, Paren}, + Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Index, Member, Pat, PatIdent, PatOr, + PatParen, PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, Token, TypePath, +}; + +with_debug_clone_and_fold! { + struct MatchPatBinding<> { + ident: Ident, + } +} + +impl ToTokens for MatchPatBinding { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { ident } = self; + ident.to_tokens(tokens); + } +} + +with_debug_clone_and_fold! { + struct MatchPatParen

{ + paren_token: Paren, + pat: Box

, + } +} + +impl ToTokens for MatchPatParen

{ + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { paren_token, pat } = self; + paren_token.surround(tokens, |tokens| pat.to_tokens(tokens)); + } +} + +with_debug_clone_and_fold! { + struct MatchPatOr

{ + leading_vert: Option, + cases: Punctuated, + } +} + +impl ToTokens for MatchPatOr

{ + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + leading_vert, + cases, + } = self; + leading_vert.to_tokens(tokens); + cases.to_tokens(tokens); + } +} + +with_debug_clone_and_fold! { + struct MatchPatWild<> { + underscore_token: Token![_], + } +} + +impl ToTokens for MatchPatWild { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { underscore_token } = self; + underscore_token.to_tokens(tokens); + } +} + +with_debug_clone_and_fold! { + struct MatchPatStructField<> { + member: Member, + colon_token: Option, + pat: MatchPatSimple, + } +} + +impl ToTokens for MatchPatStructField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + member, + colon_token, + pat, + } = self; + member.to_tokens(tokens); + colon_token.to_tokens(tokens); + pat.to_tokens(tokens); + } +} + +impl MatchPatStructField { + fn parse(state: &mut HdlMatchParseState<'_>, field_pat: FieldPat) -> Result { + let FieldPat { + attrs: _, + member, + colon_token, + pat, + } = field_pat; + Ok(Self { + member, + colon_token, + pat: MatchPatSimple::parse(state, *pat)?, + }) + } +} + +with_debug_clone_and_fold! { + struct MatchPatStruct<> { + resolved_path: Path, + brace_token: Brace, + fields: Punctuated, + rest: Option, + } +} + +impl ToTokens for MatchPatStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + resolved_path, + brace_token, + fields, + rest, + } = self; + resolved_path.to_tokens(tokens); + brace_token.surround(tokens, |tokens| { + fields.to_tokens(tokens); + rest.to_tokens(tokens); + }) + } +} + +#[derive(Debug, Clone)] +enum MatchPatSimple { + Paren(MatchPatParen), + Or(MatchPatOr), + Binding(MatchPatBinding), + Wild(MatchPatWild), +} + +impl_fold! { + enum MatchPatSimple<> { + Paren(MatchPatParen), + Or(MatchPatOr), + Binding(MatchPatBinding), + Wild(MatchPatWild), + } +} + +impl ToTokens for MatchPatSimple { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Or(v) => v.to_tokens(tokens), + Self::Paren(v) => v.to_tokens(tokens), + Self::Binding(v) => v.to_tokens(tokens), + Self::Wild(v) => v.to_tokens(tokens), + } + } +} + +fn is_pat_ident_a_struct_or_enum_name(ident: &Ident) -> bool { + ident + .to_string() + .starts_with(|ch: char| ch.is_ascii_uppercase()) +} + +trait ParseMatchPat: Sized { + fn simple(v: MatchPatSimple) -> Self; + fn or(v: MatchPatOr) -> Self; + fn paren(v: MatchPatParen) -> Self; + fn struct_( + state: &mut HdlMatchParseState<'_>, + v: MatchPatStruct, + struct_error_spanned: &dyn ToTokens, + ) -> Result; + fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result { + match pat { + Pat::Ident(PatIdent { + attrs: _, + by_ref, + mutability, + ident, + subpat, + }) => { + if let Some(by_ref) = by_ref { + state + .errors + .error(by_ref, "ref not allowed in #[hdl] patterns"); + } + if let Some(mutability) = mutability { + state + .errors + .error(mutability, "mut not allowed in #[hdl] patterns"); + } + if let Some((at_token, _)) = subpat { + state + .errors + .error(at_token, "@ not allowed in #[hdl] patterns"); + } + if is_pat_ident_a_struct_or_enum_name(&ident) { + let ident_span = ident.span(); + let resolved_path = state.resolve_enum_struct_path(TypePath { + qself: None, + path: ident.clone().into(), + })?; + Self::struct_( + state, + MatchPatStruct { + resolved_path, + brace_token: Brace(ident_span), + fields: Punctuated::new(), + rest: None, + }, + &ident, + ) + } else { + Ok(Self::simple(MatchPatSimple::Binding(MatchPatBinding { + ident, + }))) + } + } + Pat::Or(PatOr { + attrs: _, + leading_vert, + cases, + }) => Ok(Self::or(MatchPatOr { + leading_vert, + cases: cases + .into_pairs() + .filter_map(|pair| { + let (pat, punct) = pair.into_tuple(); + let pat = Self::parse(state, pat).ok()?; + Some(Pair::new(pat, punct)) + }) + .collect(), + })), + Pat::Paren(PatParen { + attrs: _, + paren_token, + pat, + }) => Ok(Self::paren(MatchPatParen { + paren_token, + pat: Box::new(Self::parse(state, *pat)?), + })), + Pat::Path(PatPath { + attrs: _, + qself, + path, + }) => { + let path = TypePath { qself, path }; + let path_span = path.span(); + let resolved_path = state.resolve_enum_struct_path(path.clone())?; + Self::struct_( + state, + MatchPatStruct { + resolved_path, + brace_token: Brace(path_span), + fields: Punctuated::new(), + rest: None, + }, + &path, + ) + } + Pat::Struct(PatStruct { + attrs: _, + qself, + path, + brace_token, + fields, + rest, + }) => { + let fields = fields + .into_pairs() + .filter_map(|pair| { + let (field_pat, punct) = pair.into_tuple(); + let field_pat = MatchPatStructField::parse(state, field_pat).ok()?; + Some(Pair::new(field_pat, punct)) + }) + .collect(); + let path = TypePath { qself, path }; + let resolved_path = state.resolve_enum_struct_path(path.clone())?; + Self::struct_( + state, + MatchPatStruct { + resolved_path, + brace_token, + fields, + rest: rest.map( + |PatRest { + attrs: _, + dot2_token, + }| dot2_token, + ), + }, + &path, + ) + } + Pat::TupleStruct(PatTupleStruct { + attrs: _, + qself, + path, + paren_token, + mut elems, + }) => { + let rest = if let Some(&Pat::Rest(PatRest { + attrs: _, + dot2_token, + })) = elems.last() + { + elems.pop(); + Some(dot2_token) + } else { + None + }; + let fields = elems + .into_pairs() + .enumerate() + .filter_map(|(index, pair)| { + let (pat, punct) = pair.into_tuple(); + let pat = MatchPatSimple::parse(state, pat).ok()?; + let mut index = Index::from(index); + index.span = state.span; + let field = MatchPatStructField { + member: index.into(), + colon_token: Some(Token![:](state.span)), + pat, + }; + Some(Pair::new(field, punct)) + }) + .collect(); + let path = TypePath { qself, path }; + let resolved_path = state.resolve_enum_struct_path(path.clone())?; + Self::struct_( + state, + MatchPatStruct { + resolved_path, + brace_token: Brace { + span: paren_token.span, + }, + fields, + rest, + }, + &path, + ) + } + Pat::Rest(_) => { + state + .errors + .error(pat, "not allowed here in #[hdl] patterns"); + Err(()) + } + Pat::Wild(PatWild { + attrs: _, + underscore_token, + }) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild { + underscore_token, + }))), + Pat::Tuple(_) | Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => { + state + .errors + .error(pat, "not yet implemented in #[hdl] patterns"); + Err(()) + } + _ => { + state.errors.error(pat, "not allowed in #[hdl] patterns"); + Err(()) + } + } + } +} + +impl ParseMatchPat for MatchPatSimple { + fn simple(v: MatchPatSimple) -> Self { + v + } + + fn or(v: MatchPatOr) -> Self { + Self::Or(v) + } + + fn paren(v: MatchPatParen) -> Self { + Self::Paren(v) + } + + fn struct_( + state: &mut HdlMatchParseState<'_>, + _v: MatchPatStruct, + struct_error_spanned: &dyn ToTokens, + ) -> Result { + state.errors.error( + struct_error_spanned, + "not yet implemented inside structs/enums in #[hdl] patterns", + ); + Err(()) + } +} + +#[derive(Debug, Clone)] +enum MatchPat { + Simple(MatchPatSimple), + Or(MatchPatOr), + Paren(MatchPatParen), + Struct(MatchPatStruct), +} + +impl_fold! { + enum MatchPat<> { + Simple(MatchPatSimple), + Or(MatchPatOr), + Paren(MatchPatParen), + Struct(MatchPatStruct), + } +} + +impl ParseMatchPat for MatchPat { + fn simple(v: MatchPatSimple) -> Self { + Self::Simple(v) + } + + fn or(v: MatchPatOr) -> Self { + Self::Or(v) + } + + fn paren(v: MatchPatParen) -> Self { + Self::Paren(v) + } + + fn struct_( + _state: &mut HdlMatchParseState<'_>, + v: MatchPatStruct, + _struct_error_spanned: &dyn ToTokens, + ) -> Result { + Ok(Self::Struct(v)) + } +} + +impl ToTokens for MatchPat { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Simple(v) => v.to_tokens(tokens), + Self::Or(v) => v.to_tokens(tokens), + Self::Paren(v) => v.to_tokens(tokens), + Self::Struct(v) => v.to_tokens(tokens), + } + } +} + +with_debug_clone_and_fold! { + struct MatchArm<> { + attrs: Vec, + pat: MatchPat, + fat_arrow_token: Token![=>], + body: Box, + comma: Option, + } +} + +impl MatchArm { + fn parse(state: &mut HdlMatchParseState<'_>, arm: Arm) -> Result { + let Arm { + attrs, + pat, + guard, + fat_arrow_token, + body, + comma, + } = arm; + if let Some((if_, _)) = guard { + state + .errors + .error(if_, "#[hdl] match arm if clauses are not implemented"); + } + Ok(Self { + attrs, + pat: MatchPat::parse(state, pat)?, + fat_arrow_token, + body, + comma, + }) + } +} + +impl ToTokens for MatchArm { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + pat, + fat_arrow_token, + body, + comma, + } = self; + tokens.append_all(attrs); + pat.to_tokens(tokens); + fat_arrow_token.to_tokens(tokens); + body.to_tokens(tokens); + comma.to_tokens(tokens); + } +} + +struct RewriteAsCheckMatch { + span: Span, +} + +impl Fold for RewriteAsCheckMatch { + fn fold_field_pat(&mut self, mut i: FieldPat) -> FieldPat { + i.colon_token = Some(Token![:](i.member.span())); + i + } + fn fold_pat(&mut self, i: Pat) -> Pat { + match i { + Pat::Ident(PatIdent { + attrs, + by_ref, + mutability, + ident, + subpat: None, + }) if is_pat_ident_a_struct_or_enum_name(&ident) => { + parse_quote_spanned! {ident.span()=> + #(#attrs)* + #by_ref + #mutability + #ident {} + } + } + _ => fold_pat(self, i), + } + } + fn fold_pat_ident(&mut self, mut i: PatIdent) -> PatIdent { + i.by_ref = Some(Token![ref](i.ident.span())); + i.mutability = None; + i + } + fn fold_arm(&mut self, mut i: Arm) -> Arm { + i.body = parse_quote_spanned! {self.span=> + match __infallible {} + }; + i.comma.get_or_insert_with(|| Token![,](self.span)); + fold_arm(self, i) + } + fn fold_expr_match(&mut self, mut i: ExprMatch) -> ExprMatch { + i.expr = parse_quote_spanned! {self.span=> + __match_value + }; + fold_expr_match(self, i) + } + fn fold_expr(&mut self, i: Expr) -> Expr { + // don't recurse into expressions + i + } +} + +struct HdlMatchParseState<'a> { + errors: &'a mut Errors, + span: Span, +} + +impl HdlMatchParseState<'_> { + fn resolve_enum_struct_path(&mut self, path: TypePath) -> Result { + let StructOrEnumPath { ty, variant } = + StructOrEnumPath::new(self.errors, path, &AggregateLiteralOptions::default())?; + Ok(if let Some((_variant_path, variant_name)) = variant { + parse_quote_spanned! {self.span=> + __MatchTy::<#ty>::#variant_name + } + } else { + parse_quote_spanned! {self.span=> + __MatchTy::<#ty> + } + }) + } +} + +impl Visitor { + pub(crate) fn process_hdl_match( + &mut self, + _hdl_attr: HdlAttr, + expr_match: ExprMatch, + ) -> Expr { + let span = expr_match.match_token.span(); + let check_match = RewriteAsCheckMatch { span }.fold_expr_match(expr_match.clone()); + let ExprMatch { + attrs: _, + match_token, + expr, + brace_token: _, + arms, + } = expr_match; + self.require_normal_module(match_token); + let mut state = HdlMatchParseState { + errors: &mut self.errors, + span, + }; + let arms = Vec::from_iter( + arms.into_iter() + .filter_map(|arm| MatchArm::parse(&mut state, arm).ok()), + ); + parse_quote_spanned! {span=> + { + type __MatchTy = <::Type as ::fayalite::ty::Type>::MatchVariant; + let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); + ::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| { + #[allow(unused_variables)] + #check_match + }); + for __match_variant in m.match_(__match_expr) { + let (__match_variant, __scope) = ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope(__match_variant); + #match_token __match_variant { + #(#arms)* + } + } + } + } + } +} diff --git a/crates/fayalite-proc-macros-impl/src/value_derive_common.rs b/crates/fayalite-proc-macros-impl/src/value_derive_common.rs new file mode 100644 index 0000000..b9c6ffb --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/value_derive_common.rs @@ -0,0 +1,746 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{fold::impl_fold, kw, Errors, HdlAttr}; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use std::collections::{BTreeMap, HashMap, HashSet}; +use syn::{ + fold::{fold_generics, Fold}, + parse::{Parse, ParseStream}, + parse_quote, parse_quote_spanned, + punctuated::Punctuated, + spanned::Spanned, + token::{Brace, Paren, Where}, + Block, ConstParam, Expr, Field, Fields, FieldsNamed, FieldsUnnamed, GenericParam, Generics, + Ident, Index, ItemImpl, Lifetime, LifetimeParam, Member, Path, Token, Type, TypeParam, + TypePath, Visibility, WhereClause, WherePredicate, +}; + +#[derive(Clone, Debug)] +pub(crate) struct Bounds(pub(crate) Punctuated); + +impl_fold! { + struct Bounds<>(Punctuated); +} + +impl Parse for Bounds { + fn parse(input: ParseStream) -> syn::Result { + Ok(Bounds(Punctuated::parse_terminated(input)?)) + } +} + +impl From> for Bounds { + fn from(value: Option) -> Self { + Self(value.map_or_else(Punctuated::new, |v| v.predicates)) + } +} + +impl ToTokens for Bounds { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0.to_tokens(tokens) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ParsedField { + pub(crate) options: HdlAttr, + pub(crate) vis: Visibility, + pub(crate) name: Member, + pub(crate) ty: Type, +} + +impl ParsedField { + pub(crate) fn var_name(&self) -> Ident { + format_ident!("__v_{}", self.name) + } +} + +pub(crate) fn get_field_name( + index: usize, + name: Option, + ty_span: impl FnOnce() -> Span, +) -> Member { + match name { + Some(name) => Member::Named(name), + None => Member::Unnamed(Index { + index: index as _, + span: ty_span(), + }), + } +} + +pub(crate) fn get_field_names(fields: &Fields) -> impl Iterator + '_ { + fields + .iter() + .enumerate() + .map(|(index, field)| get_field_name(index, field.ident.clone(), || field.ty.span())) +} + +impl ParsedField { + pub(crate) fn parse_fields( + errors: &mut Errors, + fields: &mut Fields, + in_enum: bool, + ) -> (FieldsKind, Vec>) { + let mut unit_fields = Punctuated::new(); + let (fields_kind, fields) = match fields { + Fields::Named(fields) => (FieldsKind::Named(fields.brace_token), &mut fields.named), + Fields::Unnamed(fields) => { + (FieldsKind::Unnamed(fields.paren_token), &mut fields.unnamed) + } + Fields::Unit => (FieldsKind::Unit, &mut unit_fields), + }; + let fields = fields + .iter_mut() + .enumerate() + .map(|(index, field)| { + let options = errors + .unwrap_or_default(HdlAttr::parse_and_take_attr(&mut field.attrs)) + .unwrap_or_default(); + let name = get_field_name(index, field.ident.clone(), || field.ty.span()); + if in_enum && !matches!(field.vis, Visibility::Inherited) { + errors.error(&field.vis, "field visibility not allowed in enums"); + } + ParsedField { + options, + vis: field.vis.clone(), + name, + ty: field.ty.clone(), + } + }) + .collect(); + (fields_kind, fields) + } +} + +#[derive(Copy, Clone, Debug)] +pub(crate) enum FieldsKind { + Unit, + Named(Brace), + Unnamed(Paren), +} + +impl FieldsKind { + pub(crate) fn into_fields_named( + brace_token: Brace, + fields: impl IntoIterator, + ) -> Fields { + Fields::Named(FieldsNamed { + brace_token, + named: Punctuated::from_iter(fields), + }) + } + pub(crate) fn into_fields_unnamed( + paren_token: Paren, + fields: impl IntoIterator, + ) -> Fields { + Fields::Unnamed(FieldsUnnamed { + paren_token, + unnamed: Punctuated::from_iter(fields), + }) + } + pub(crate) fn into_fields(self, fields: impl IntoIterator) -> Fields { + match self { + FieldsKind::Unit => { + let mut fields = fields.into_iter().peekable(); + let Some(first_field) = fields.peek() else { + return Fields::Unit; + }; + if first_field.ident.is_some() { + Self::into_fields_named(Default::default(), fields) + } else { + Self::into_fields_unnamed(Default::default(), fields) + } + } + FieldsKind::Named(brace_token) => Self::into_fields_named(brace_token, fields), + FieldsKind::Unnamed(paren_token) => Self::into_fields_unnamed(paren_token, fields), + } + } +} + +pub(crate) fn get_target(target: &Option<(kw::target, Paren, Path)>, item_ident: &Ident) -> Path { + match target { + Some((_, _, target)) => target.clone(), + None => item_ident.clone().into(), + } +} + +pub(crate) struct ValueDeriveGenerics { + pub(crate) generics: Generics, + pub(crate) fixed_type_generics: Generics, +} + +impl ValueDeriveGenerics { + pub(crate) fn get(mut generics: Generics, where_: &Option<(Where, Paren, Bounds)>) -> Self { + let mut fixed_type_generics = generics.clone(); + if let Some((_, _, bounds)) = where_ { + generics + .make_where_clause() + .predicates + .extend(bounds.0.iter().cloned()); + fixed_type_generics + .where_clause + .clone_from(&generics.where_clause); + } else { + let type_params = Vec::from_iter(generics.type_params().map(|v| v.ident.clone())); + let predicates = &mut generics.make_where_clause().predicates; + let fixed_type_predicates = &mut fixed_type_generics.make_where_clause().predicates; + for type_param in type_params { + predicates.push(parse_quote! {#type_param: ::fayalite::ty::Value}); + predicates.push(parse_quote! {<#type_param as ::fayalite::expr::ToExpr>::Type: ::fayalite::ty::Type}); + fixed_type_predicates.push(parse_quote! {#type_param: ::fayalite::ty::Value}); + fixed_type_predicates.push(parse_quote! {<#type_param as ::fayalite::expr::ToExpr>::Type: ::fayalite::ty::FixedType}); + fixed_type_predicates.push(parse_quote! {<<#type_param as ::fayalite::expr::ToExpr>::Type as ::fayalite::ty::Type>::MaskType: ::fayalite::ty::FixedType}); + } + } + Self { + generics, + fixed_type_generics, + } + } +} + +pub(crate) fn derive_clone_hash_eq_partialeq_for_struct( + the_struct_ident: &Ident, + generics: &Generics, + field_names: &[Name], +) -> TokenStream { + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + quote! { + #[automatically_derived] + impl #impl_generics ::fayalite::__std::clone::Clone for #the_struct_ident #type_generics + #where_clause + { + fn clone(&self) -> Self { + Self { + #(#field_names: ::fayalite::__std::clone::Clone::clone(&self.#field_names),)* + } + } + } + + #[automatically_derived] + impl #impl_generics ::fayalite::__std::hash::Hash for #the_struct_ident #type_generics + #where_clause + { + #[allow(unused_variables)] + fn hash<__H: ::fayalite::__std::hash::Hasher>(&self, hasher: &mut __H) { + #(::fayalite::__std::hash::Hash::hash(&self.#field_names, hasher);)* + } + } + + #[automatically_derived] + impl #impl_generics ::fayalite::__std::cmp::Eq for #the_struct_ident #type_generics + #where_clause + { + } + + #[automatically_derived] + impl #impl_generics ::fayalite::__std::cmp::PartialEq for #the_struct_ident #type_generics + #where_clause + { + #[allow(unused_variables)] + #[allow(clippy::nonminimal_bool)] + fn eq(&self, other: &Self) -> ::fayalite::__std::primitive::bool { + true + #(&& ::fayalite::__std::cmp::PartialEq::eq(&self.#field_names, &other.#field_names))* + } + } + } +} + +pub(crate) fn append_field(fields: &mut Fields, mut field: Field) -> Member { + let ident = field.ident.clone().expect("ident is supplied"); + match fields { + Fields::Named(FieldsNamed { named, .. }) => { + named.push(field); + Member::Named(ident) + } + Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { + field.ident = None; + field.colon_token = None; + let index = unnamed.len(); + unnamed.push(field); + Member::Unnamed(index.into()) + } + Fields::Unit => { + *fields = Fields::Named(FieldsNamed { + brace_token: Default::default(), + named: Punctuated::from_iter([field]), + }); + Member::Named(ident) + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct BuilderField { + pub(crate) names: HashSet, + pub(crate) mapped_value: Expr, + pub(crate) mapped_type: Type, + pub(crate) where_clause: Option, + pub(crate) builder_field_name: Ident, + pub(crate) type_param: Ident, +} + +#[derive(Debug)] +pub(crate) struct Builder { + struct_name: Ident, + vis: Visibility, + fields: BTreeMap, +} + +#[derive(Debug)] +pub(crate) struct BuilderWithFields { + struct_name: Ident, + vis: Visibility, + phantom_type_param: Ident, + phantom_type_field: Ident, + fields: Vec<(String, BuilderField)>, +} + +impl Builder { + pub(crate) fn new(struct_name: Ident, vis: Visibility) -> Self { + Self { + struct_name, + vis, + fields: BTreeMap::new(), + } + } + pub(crate) fn insert_field( + &mut self, + name: Member, + map_value: impl FnOnce(&Ident) -> Expr, + map_type: impl FnOnce(&Ident) -> Type, + where_clause: impl FnOnce(&Ident) -> Option, + ) { + self.fields + .entry(name.to_token_stream().to_string()) + .or_insert_with_key(|name| { + let builder_field_name = + format_ident!("field_{}", name, span = self.struct_name.span()); + let type_param = format_ident!("__T_{}", name, span = self.struct_name.span()); + BuilderField { + names: HashSet::new(), + mapped_value: map_value(&builder_field_name), + mapped_type: map_type(&type_param), + where_clause: where_clause(&type_param), + builder_field_name, + type_param, + } + }) + .names + .insert(name); + } + pub(crate) fn finish_filling_in_fields(self) -> BuilderWithFields { + let Self { + struct_name, + vis, + fields, + } = self; + let fields = Vec::from_iter(fields); + BuilderWithFields { + phantom_type_param: Ident::new("__Phantom", struct_name.span()), + phantom_type_field: Ident::new("__phantom", struct_name.span()), + struct_name, + vis, + fields, + } + } +} + +impl BuilderWithFields { + pub(crate) fn get_field(&self, name: &Member) -> Option<(usize, &BuilderField)> { + let index = self + .fields + .binary_search_by_key(&&*name.to_token_stream().to_string(), |v| &*v.0) + .ok()?; + Some((index, &self.fields[index].1)) + } + pub(crate) fn ty( + &self, + specified_fields: impl IntoIterator, + phantom_type: Option<&Type>, + other_fields_are_any_type: bool, + ) -> TypePath { + let Self { + struct_name, + vis: _, + phantom_type_param, + phantom_type_field: _, + fields, + } = self; + let span = struct_name.span(); + let mut arguments = + Vec::from_iter(fields.iter().map(|(_, BuilderField { type_param, .. })| { + if other_fields_are_any_type { + parse_quote_spanned! {span=> + #type_param + } + } else { + parse_quote_spanned! {span=> + () + } + } + })); + for (name, ty) in specified_fields { + let Some((index, _)) = self.get_field(&name) else { + panic!("field not found: {}", name.to_token_stream()); + }; + arguments[index] = ty; + } + let phantom_type_param = phantom_type.is_none().then_some(phantom_type_param); + parse_quote_spanned! {span=> + #struct_name::<#phantom_type_param #phantom_type #(, #arguments)*> + } + } + pub(crate) fn append_generics( + &self, + specified_fields: impl IntoIterator, + has_phantom_type_param: bool, + other_fields_are_any_type: bool, + generics: &mut Generics, + ) { + let Self { + struct_name: _, + vis: _, + phantom_type_param, + phantom_type_field: _, + fields, + } = self; + if has_phantom_type_param { + generics.params.push(GenericParam::from(TypeParam::from( + phantom_type_param.clone(), + ))); + } + if !other_fields_are_any_type { + return; + } + let mut type_params = Vec::from_iter( + fields + .iter() + .map(|(_, BuilderField { type_param, .. })| Some(type_param)), + ); + for name in specified_fields { + let Some((index, _)) = self.get_field(&name) else { + panic!("field not found: {}", name.to_token_stream()); + }; + type_params[index] = None; + } + generics.params.extend( + type_params + .into_iter() + .filter_map(|v| Some(GenericParam::from(TypeParam::from(v?.clone())))), + ); + } + pub(crate) fn make_build_method( + &self, + build_fn_name: &Ident, + specified_fields: impl IntoIterator, + generics: &Generics, + phantom_type: &Type, + return_ty: &Type, + mut body: Block, + ) -> ItemImpl { + let Self { + struct_name, + vis, + phantom_type_param: _, + phantom_type_field, + fields, + } = self; + let span = struct_name.span(); + let field_names = Vec::from_iter(fields.iter().map(|v| &v.1.builder_field_name)); + let (impl_generics, _type_generics, where_clause) = generics.split_for_impl(); + let empty_arg = parse_quote_spanned! {span=> + () + }; + let mut ty_arguments = vec![empty_arg; fields.len()]; + let empty_field_pat = quote_spanned! {span=> + : _ + }; + let mut field_pats = vec![Some(empty_field_pat); fields.len()]; + for (name, ty) in specified_fields { + let Some((index, _)) = self.get_field(&name) else { + panic!("field not found: {}", name.to_token_stream()); + }; + ty_arguments[index] = ty; + field_pats[index] = None; + } + body.stmts.insert( + 0, + parse_quote_spanned! {span=> + let Self { + #(#field_names #field_pats,)* + #phantom_type_field: _, + } = self; + }, + ); + parse_quote_spanned! {span=> + #[automatically_derived] + impl #impl_generics #struct_name<#phantom_type #(, #ty_arguments)*> + #where_clause + { + #[allow(non_snake_case, dead_code)] + #vis fn #build_fn_name(self) -> #return_ty + #body + } + } + } +} + +impl ToTokens for BuilderWithFields { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + struct_name, + vis, + phantom_type_param, + phantom_type_field, + fields, + } = self; + let span = struct_name.span(); + let mut any_generics = Generics::default(); + self.append_generics([], true, true, &mut any_generics); + let empty_ty = self.ty([], None, false); + let field_names = Vec::from_iter(fields.iter().map(|v| &v.1.builder_field_name)); + let field_type_params = Vec::from_iter(fields.iter().map(|v| &v.1.type_param)); + quote_spanned! {span=> + #[allow(non_camel_case_types)] + #[non_exhaustive] + #vis struct #struct_name #any_generics { + #(#field_names: #field_type_params,)* + #phantom_type_field: ::fayalite::__std::marker::PhantomData<#phantom_type_param>, + } + + #[automatically_derived] + impl<#phantom_type_param> #empty_ty { + fn new() -> Self { + Self { + #(#field_names: (),)* + #phantom_type_field: ::fayalite::__std::marker::PhantomData, + } + } + } + } + .to_tokens(tokens); + for (field_index, (_, field)) in self.fields.iter().enumerate() { + let initial_fields = &fields[..field_index]; + let final_fields = &fields[field_index..][1..]; + let initial_type_params = + Vec::from_iter(initial_fields.iter().map(|v| &v.1.type_param)); + let final_type_params = Vec::from_iter(final_fields.iter().map(|v| &v.1.type_param)); + let initial_field_names = + Vec::from_iter(initial_fields.iter().map(|v| &v.1.builder_field_name)); + let final_field_names = + Vec::from_iter(final_fields.iter().map(|v| &v.1.builder_field_name)); + let BuilderField { + names: _, + mapped_value, + mapped_type, + where_clause, + builder_field_name, + type_param, + } = field; + quote_spanned! {span=> + #[automatically_derived] + #[allow(non_camel_case_types, dead_code)] + impl<#phantom_type_param #(, #initial_type_params)* #(, #final_type_params)*> #struct_name<#phantom_type_param #(, #initial_type_params)*, () #(, #final_type_params)*> { + #vis fn #builder_field_name<#type_param>(self, #builder_field_name: #type_param) -> #struct_name<#phantom_type_param #(, #initial_type_params)*, #mapped_type #(, #final_type_params)*> + #where_clause + { + let Self { + #(#initial_field_names,)* + #builder_field_name: (), + #(#final_field_names,)* + #phantom_type_field: _, + } = self; + let #builder_field_name = #mapped_value; + #struct_name { + #(#field_names,)* + #phantom_type_field: ::fayalite::__std::marker::PhantomData, + } + } + } + } + .to_tokens(tokens); + } + } +} + +pub(crate) struct MapIdents { + pub(crate) map: HashMap, +} + +impl Fold for &MapIdents { + fn fold_ident(&mut self, i: Ident) -> Ident { + self.map.get(&i).cloned().unwrap_or(i) + } +} + +pub(crate) struct DupGenerics { + pub(crate) combined: Generics, + pub(crate) maps: M, +} + +pub(crate) fn merge_punctuated( + target: &mut Punctuated, + source: Punctuated, + make_punct: impl FnOnce() -> P, +) { + if source.is_empty() { + return; + } + if target.is_empty() { + *target = source; + return; + } + if !target.trailing_punct() { + target.push_punct(make_punct()); + } + target.extend(source.into_pairs()); +} + +pub(crate) fn merge_generics(target: &mut Generics, source: Generics) { + let Generics { + lt_token, + params, + gt_token, + where_clause, + } = source; + let span = lt_token.map(|v| v.span).unwrap_or_else(|| params.span()); + target.lt_token = target.lt_token.or(lt_token); + merge_punctuated(&mut target.params, params, || Token![,](span)); + target.gt_token = target.gt_token.or(gt_token); + if let Some(where_clause) = where_clause { + if let Some(target_where_clause) = &mut target.where_clause { + let WhereClause { + where_token, + predicates, + } = where_clause; + let span = where_token.span; + target_where_clause.where_token = where_token; + merge_punctuated(&mut target_where_clause.predicates, predicates, || { + Token![,](span) + }); + } else { + target.where_clause = Some(where_clause); + } + } +} + +impl DupGenerics> { + pub(crate) fn new_dyn(generics: &Generics, count: usize) -> Self { + let mut maps = Vec::from_iter((0..count).map(|_| MapIdents { + map: HashMap::new(), + })); + for param in &generics.params { + let (GenericParam::Lifetime(LifetimeParam { + lifetime: Lifetime { ident, .. }, + .. + }) + | GenericParam::Type(TypeParam { ident, .. }) + | GenericParam::Const(ConstParam { ident, .. })) = param; + for (i, map_idents) in maps.iter_mut().enumerate() { + map_idents + .map + .insert(ident.clone(), format_ident!("__{}_{}", ident, i)); + } + } + let mut combined = Generics::default(); + for map_idents in maps.iter() { + merge_generics( + &mut combined, + fold_generics(&mut { map_idents }, generics.clone()), + ); + } + Self { combined, maps } + } +} + +impl DupGenerics<[MapIdents; COUNT]> { + pub(crate) fn new(generics: &Generics) -> Self { + let DupGenerics { combined, maps } = DupGenerics::new_dyn(generics, COUNT); + Self { + combined, + maps: maps.try_into().ok().unwrap(), + } + } +} + +pub(crate) fn add_where_predicate( + target: &mut Generics, + span: Span, + where_predicate: WherePredicate, +) { + let WhereClause { + where_token: _, + predicates, + } = target.where_clause.get_or_insert_with(|| WhereClause { + where_token: Token![where](span), + predicates: Punctuated::new(), + }); + if !predicates.empty_or_trailing() { + predicates.push_punct(Token![,](span)); + } + predicates.push_value(where_predicate); +} + +pub(crate) fn make_connect_impl( + connect_inexact: Option<(crate::kw::connect_inexact,)>, + generics: &Generics, + ty_ident: &Ident, + field_types: impl IntoIterator, +) -> TokenStream { + let span = ty_ident.span(); + let impl_generics; + let combined_generics; + let where_clause; + let lhs_generics; + let lhs_type_generics; + let rhs_generics; + let rhs_type_generics; + if connect_inexact.is_some() { + let DupGenerics { + mut combined, + maps: [lhs_map, rhs_map], + } = DupGenerics::new(generics); + for field_type in field_types { + let lhs_type = (&lhs_map).fold_type(field_type.clone()); + let rhs_type = (&rhs_map).fold_type(field_type); + add_where_predicate( + &mut combined, + span, + parse_quote_spanned! {span=> + #lhs_type: ::fayalite::ty::Connect<#rhs_type> + }, + ); + } + combined_generics = combined; + (impl_generics, _, where_clause) = combined_generics.split_for_impl(); + lhs_generics = (&lhs_map).fold_generics(generics.clone()); + (_, lhs_type_generics, _) = lhs_generics.split_for_impl(); + rhs_generics = (&rhs_map).fold_generics(generics.clone()); + (_, rhs_type_generics, _) = rhs_generics.split_for_impl(); + } else { + let mut generics = generics.clone(); + for field_type in field_types { + add_where_predicate( + &mut generics, + span, + parse_quote_spanned! {span=> + #field_type: ::fayalite::ty::Connect<#field_type> + }, + ); + } + combined_generics = generics; + (impl_generics, lhs_type_generics, where_clause) = combined_generics.split_for_impl(); + rhs_type_generics = lhs_type_generics.clone(); + } + quote_spanned! {span=> + #[automatically_derived] + #[allow(non_camel_case_types)] + impl #impl_generics ::fayalite::ty::Connect<#ty_ident #rhs_type_generics> for #ty_ident #lhs_type_generics + #where_clause + { + } + } +} diff --git a/crates/fayalite-proc-macros-impl/src/value_derive_enum.rs b/crates/fayalite-proc-macros-impl/src/value_derive_enum.rs new file mode 100644 index 0000000..2c3da08 --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/value_derive_enum.rs @@ -0,0 +1,901 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + value_derive_common::{ + append_field, derive_clone_hash_eq_partialeq_for_struct, get_field_names, get_target, + make_connect_impl, Bounds, Builder, FieldsKind, ParsedField, ValueDeriveGenerics, + }, + value_derive_struct::{self, ParsedStruct, ParsedStructNames, StructOptions}, + Errors, HdlAttr, +}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use syn::{ + parse_quote, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::Brace, + Field, FieldMutability, Fields, FieldsNamed, Generics, Ident, Index, ItemEnum, ItemStruct, + Member, Path, Token, Type, Variant, Visibility, +}; + +crate::options! { + #[options = EnumOptions] + enum EnumOption { + OutlineGenerated(outline_generated), + ConnectInexact(connect_inexact), + Bounds(where_, Bounds), + Target(target, Path), + } +} + +crate::options! { + #[options = VariantOptions] + enum VariantOption {} +} + +crate::options! { + #[options = FieldOptions] + enum FieldOption {} +} + +enum VariantValue { + None, + Direct { + value_type: Type, + }, + Struct { + value_struct: ItemStruct, + parsed_struct: ParsedStruct, + }, +} + +impl VariantValue { + fn is_none(&self) -> bool { + matches!(self, Self::None) + } + fn value_ty(&self) -> Option { + match self { + VariantValue::None => None, + VariantValue::Direct { value_type } => Some(value_type.clone()), + VariantValue::Struct { value_struct, .. } => { + let (_, type_generics, _) = value_struct.generics.split_for_impl(); + let ident = &value_struct.ident; + Some(parse_quote! { #ident #type_generics }) + } + } + } +} + +struct ParsedVariant { + options: HdlAttr, + ident: Ident, + fields_kind: FieldsKind, + fields: Vec>, + value: VariantValue, +} + +impl ParsedVariant { + fn parse( + errors: &mut Errors, + variant: Variant, + enum_options: &EnumOptions, + enum_vis: &Visibility, + enum_ident: &Ident, + enum_generics: &Generics, + ) -> Self { + let target = get_target(&enum_options.target, enum_ident); + let Variant { + mut attrs, + ident, + fields, + discriminant, + } = variant; + if let Some((eq, _)) = discriminant { + errors.error(eq, "#[derive(Value)]: discriminants not allowed"); + } + let variant_options = errors + .unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs)) + .unwrap_or_default(); + let (fields_kind, parsed_fields) = + ParsedField::parse_fields(errors, &mut fields.clone(), true); + let value = match (&fields_kind, &*parsed_fields) { + (FieldsKind::Unit, _) => VariantValue::None, + ( + FieldsKind::Unnamed(_), + [ParsedField { + options, + vis: _, + name: Member::Unnamed(Index { index: 0, span: _ }), + ty, + }], + ) => { + let FieldOptions {} = options.body; + VariantValue::Direct { + value_type: ty.clone(), + } + } + _ => { + let variant_value_struct_ident = + format_ident!("__{}__{}", enum_ident, ident, span = ident.span()); + let variant_type_struct_ident = + format_ident!("__{}__{}__Type", enum_ident, ident, span = ident.span()); + let mut value_struct_fields = fields.clone(); + let (_, type_generics, _) = enum_generics.split_for_impl(); + append_field( + &mut value_struct_fields, + Field { + attrs: vec![HdlAttr::from(value_derive_struct::FieldOptions { + flip: None, + skip: Some(Default::default()), + }) + .to_attr()], + vis: enum_vis.clone(), + mutability: FieldMutability::None, + ident: Some(Ident::new("__phantom", ident.span())), + colon_token: None, + ty: parse_quote_spanned! {ident.span()=> + ::fayalite::__std::marker::PhantomData<#target #type_generics> + }, + }, + ); + let (value_struct_fields_kind, value_struct_parsed_fields) = + ParsedField::parse_fields(errors, &mut value_struct_fields, false); + let value_struct = ItemStruct { + attrs: vec![parse_quote! { #[allow(non_camel_case_types)] }], + vis: enum_vis.clone(), + struct_token: Token![struct](ident.span()), + ident: variant_value_struct_ident.clone(), + generics: enum_generics.clone(), + fields: value_struct_fields, + semi_token: None, + }; + VariantValue::Struct { + value_struct, + parsed_struct: ParsedStruct { + options: StructOptions { + outline_generated: None, + fixed_type: Some(Default::default()), + where_: Some(( + Default::default(), + Default::default(), + ValueDeriveGenerics::get( + enum_generics.clone(), + &enum_options.where_, + ) + .fixed_type_generics + .where_clause + .into(), + )), + target: None, + connect_inexact: enum_options.connect_inexact, + } + .into(), + vis: enum_vis.clone(), + struct_token: Default::default(), + generics: enum_generics.clone(), + fields_kind: value_struct_fields_kind, + fields: value_struct_parsed_fields, + semi_token: None, // it will fill in the semicolon if needed + skip_check_fields: true, + names: ParsedStructNames { + ident: variant_value_struct_ident.clone(), + type_struct_debug_ident: Some(format!("{enum_ident}::{ident}::Type")), + type_struct_ident: variant_type_struct_ident, + match_variant_ident: None, + builder_struct_ident: None, + mask_match_variant_ident: None, + mask_type_ident: None, + mask_type_debug_ident: Some(format!( + "AsMask<{enum_ident}::{ident}>::Type" + )), + mask_value_ident: None, + mask_value_debug_ident: Some(format!("AsMask<{enum_ident}::{ident}>")), + mask_builder_struct_ident: None, + }, + }, + } + } + }; + ParsedVariant { + options: variant_options, + ident, + fields_kind, + fields: parsed_fields, + value, + } + } +} + +struct ParsedEnum { + options: HdlAttr, + vis: Visibility, + enum_token: Token![enum], + ident: Ident, + generics: Generics, + brace_token: Brace, + variants: Vec, +} + +impl ParsedEnum { + fn parse(item: ItemEnum) -> syn::Result { + let ItemEnum { + mut attrs, + vis, + enum_token, + ident, + generics, + brace_token, + variants, + } = item; + let mut errors = Errors::new(); + let enum_options = errors + .unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs)) + .unwrap_or_default(); + let variants = variants + .into_iter() + .map(|variant| { + ParsedVariant::parse( + &mut errors, + variant, + &enum_options.body, + &vis, + &ident, + &generics, + ) + }) + .collect(); + errors.finish()?; + Ok(ParsedEnum { + options: enum_options, + vis, + enum_token, + ident, + generics, + brace_token, + variants, + }) + } +} + +impl ToTokens for ParsedEnum { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + options, + vis, + enum_token, + ident: enum_ident, + generics: enum_generics, + brace_token, + variants, + } = self; + let EnumOptions { + outline_generated: _, + connect_inexact, + where_, + target, + } = &options.body; + let target = get_target(target, enum_ident); + let ValueDeriveGenerics { + generics: _, + fixed_type_generics, + } = ValueDeriveGenerics::get(enum_generics.clone(), where_); + let (fixed_type_impl_generics, fixed_type_type_generics, fixed_type_where_clause) = + fixed_type_generics.split_for_impl(); + let type_struct_ident = format_ident!("__{}__Type", enum_ident); + let mut field_checks = vec![]; + let mut make_type_struct_variant_type = |variant: &ParsedVariant| { + let VariantOptions {} = variant.options.body; + let (value_struct, parsed_struct) = match &variant.value { + VariantValue::None => { + return None; + } + VariantValue::Direct { value_type } => { + field_checks.push(quote_spanned! {value_type.span()=> + __check_field::<#value_type>(); + }); + return Some(parse_quote! { <#value_type as ::fayalite::expr::ToExpr>::Type }); + } + VariantValue::Struct { + value_struct, + parsed_struct, + } => (value_struct, parsed_struct), + }; + value_struct.to_tokens(tokens); + parsed_struct.to_tokens(tokens); + let mut field_names = Vec::from_iter(get_field_names(&value_struct.fields)); + derive_clone_hash_eq_partialeq_for_struct( + &value_struct.ident, + &fixed_type_generics, + &field_names, + ) + .to_tokens(tokens); + field_names = Vec::from_iter( + field_names + .into_iter() + .zip(parsed_struct.fields.iter()) + .filter_map(|(member, field)| { + field.options.body.skip.is_none().then_some(member) + }), + ); + let field_name_strs = + Vec::from_iter(field_names.iter().map(|v| v.to_token_stream().to_string())); + let debug_ident = format!("{enum_ident}::{}", variant.ident); + let debug_body = match variant.fields_kind { + FieldsKind::Unit => quote! { + f.debug_struct(#debug_ident).finish() + }, + FieldsKind::Named(_) => quote! { + f.debug_struct(#debug_ident)#(.field(#field_name_strs, &self.#field_names))*.finish() + }, + FieldsKind::Unnamed(_) => quote! { + f.debug_tuple(#debug_ident)#(.field(&self.#field_names))*.finish() + }, + }; + let value_struct_ident = &value_struct.ident; + quote! { + #[automatically_derived] + impl #fixed_type_impl_generics ::fayalite::__std::fmt::Debug for #value_struct_ident #fixed_type_type_generics + #fixed_type_where_clause + { + fn fmt(&self, f: &mut ::fayalite::__std::fmt::Formatter<'_>) -> ::fayalite::__std::fmt::Result { + #debug_body + } + } + }.to_tokens(tokens); + Some( + parse_quote! { <#value_struct_ident #fixed_type_type_generics as ::fayalite::expr::ToExpr>::Type }, + ) + }; + let type_struct_variants = Punctuated::from_iter(variants.iter().filter_map(|variant| { + let VariantOptions {} = variant.options.body; + Some(Field { + attrs: vec![], + vis: vis.clone(), + mutability: FieldMutability::None, + ident: Some(variant.ident.clone()), + colon_token: None, // it will fill in the colon if needed + ty: make_type_struct_variant_type(variant)?, + }) + })); + let type_struct = ItemStruct { + attrs: vec![ + parse_quote! {#[allow(non_camel_case_types)]}, + parse_quote! {#[allow(non_snake_case)]}, + ], + vis: vis.clone(), + struct_token: Token![struct](enum_token.span), + ident: type_struct_ident, + generics: fixed_type_generics.clone(), + fields: Fields::Named(FieldsNamed { + brace_token: *brace_token, + named: type_struct_variants, + }), + semi_token: None, + }; + let type_struct_ident = &type_struct.ident; + let type_struct_debug_ident = format!("{enum_ident}::Type"); + type_struct.to_tokens(tokens); + let non_empty_variant_names = Vec::from_iter( + variants + .iter() + .filter(|v| !v.value.is_none()) + .map(|v| v.ident.clone()), + ); + let non_empty_variant_name_strs = + Vec::from_iter(non_empty_variant_names.iter().map(|v| v.to_string())); + let debug_type_body = quote! { + f.debug_struct(#type_struct_debug_ident)#(.field(#non_empty_variant_name_strs, &self.#non_empty_variant_names))*.finish() + }; + derive_clone_hash_eq_partialeq_for_struct( + type_struct_ident, + &fixed_type_generics, + &non_empty_variant_names, + ) + .to_tokens(tokens); + let variant_names = Vec::from_iter(variants.iter().map(|v| &v.ident)); + let variant_name_strs = Vec::from_iter(variant_names.iter().map(|v| v.to_string())); + let (variant_field_pats, variant_to_canonical_values): (Vec<_>, Vec<_>) = variants + .iter() + .map(|v| { + let field_names: Vec<_> = v.fields.iter().map(|field| &field.name).collect(); + let var_names: Vec<_> = v.fields.iter().map(|field| field.var_name()).collect(); + let field_pats = quote! { + #(#field_names: #var_names,)* + }; + let to_canonical_value = match &v.value { + VariantValue::None => quote! { ::fayalite::__std::option::Option::None }, + VariantValue::Direct { .. } => { + debug_assert_eq!(var_names.len(), 1); + quote! { + ::fayalite::__std::option::Option::Some( + ::fayalite::ty::DynValueTrait::to_canonical_dyn(#(#var_names)*), + ) + } + } + VariantValue::Struct { + value_struct, + parsed_struct, + } => { + let value_struct_ident = &value_struct.ident; + let phantom_field_name = &parsed_struct.fields.last().expect("missing phantom field").name; + let type_generics = fixed_type_type_generics.as_turbofish(); + quote! { + ::fayalite::__std::option::Option::Some( + ::fayalite::ty::DynValueTrait::to_canonical_dyn( + &#value_struct_ident #type_generics { + #(#field_names: ::fayalite::__std::clone::Clone::clone(#var_names),)* + #phantom_field_name: ::fayalite::__std::marker::PhantomData, + }, + ), + ) + } + } + }; + (field_pats, to_canonical_value) + }) + .unzip(); + let mut match_enum_variants = Punctuated::new(); + let mut match_enum_debug_arms = vec![]; + let mut match_enum_arms = vec![]; + let mut variant_vars = vec![]; + let mut from_canonical_type_variant_lets = vec![]; + let mut non_empty_variant_vars = vec![]; + let mut enum_type_variants = vec![]; + let mut enum_type_variants_hint = vec![]; + let match_enum_ident = format_ident!("__{}__MatchEnum", enum_ident); + let mut builder = Builder::new(format_ident!("__{}__Builder", enum_ident), vis.clone()); + for variant in variants.iter() { + for field in variant.fields.iter() { + builder.insert_field( + field.name.clone(), + |v| { + parse_quote_spanned! {v.span()=> + ::fayalite::expr::ToExpr::to_expr(&#v) + } + }, + |t| { + parse_quote_spanned! {t.span()=> + ::fayalite::expr::Expr<<<#t as ::fayalite::expr::ToExpr>::Type as ::fayalite::ty::Type>::Value> + } + }, + |t| { + parse_quote_spanned! {t.span()=> + where + #t: ::fayalite::expr::ToExpr, + } + }, + ); + } + } + let builder = builder.finish_filling_in_fields(); + builder.to_tokens(tokens); + for (variant_index, variant) in variants.iter().enumerate() { + let variant_var = format_ident!("__v_{}", variant.ident); + let variant_name = &variant.ident; + let variant_name_str = variant.ident.to_string(); + match_enum_variants.push(Variant { + attrs: vec![], + ident: variant.ident.clone(), + fields: variant.fields_kind.into_fields(variant.fields.iter().map( + |ParsedField { + options, + vis, + name, + ty, + }| { + let FieldOptions {} = options.body; + Field { + attrs: vec![], + vis: vis.clone(), + mutability: FieldMutability::None, + ident: if let Member::Named(name) = name { + Some(name.clone()) + } else { + None + }, + colon_token: None, + ty: parse_quote! { ::fayalite::expr::Expr<#ty> }, + } + }, + )), + discriminant: None, + }); + let match_enum_field_names = Vec::from_iter(variant.fields.iter().map( + |ParsedField { + options, + vis: _, + name, + ty: _, + }| { + let FieldOptions {} = options.body; + name + }, + )); + let match_enum_field_name_strs = Vec::from_iter(variant.fields.iter().map( + |ParsedField { + options, + vis: _, + name, + ty: _, + }| { + let FieldOptions {} = options.body; + name.to_token_stream().to_string() + }, + )); + let match_enum_debug_vars = Vec::from_iter(variant.fields.iter().map( + |ParsedField { + options, + vis: _, + name, + ty: _, + }| { + let FieldOptions {} = options.body; + format_ident!("__v_{}", name) + }, + )); + match_enum_debug_arms.push(match variant.fields_kind { + FieldsKind::Unit | FieldsKind::Named(_) => quote! { + Self::#variant_name { + #(#match_enum_field_names: ref #match_enum_debug_vars,)* + } => f.debug_struct(#variant_name_str) + #(.field(#match_enum_field_name_strs, #match_enum_debug_vars))* + .finish(), + }, + FieldsKind::Unnamed(_) => quote! { + Self::#variant_name( + #(ref #match_enum_debug_vars,)* + ) => f.debug_tuple(#variant_name_str) + #(.field(#match_enum_debug_vars))* + .finish(), + }, + }); + if let Some(value_ty) = variant.value.value_ty() { + from_canonical_type_variant_lets.push(quote! { + let #variant_var = #variant_var.from_canonical_type_helper_has_value(#variant_name_str); + }); + non_empty_variant_vars.push(variant_var.clone()); + enum_type_variants.push(quote! { + ::fayalite::enum_::VariantType { + name: ::fayalite::intern::Intern::intern(#variant_name_str), + ty: ::fayalite::__std::option::Option::Some( + ::fayalite::ty::DynType::canonical_dyn(&self.#variant_name), + ), + } + }); + enum_type_variants_hint.push(quote! { + ::fayalite::enum_::VariantType { + name: ::fayalite::intern::Intern::intern(#variant_name_str), + ty: ::fayalite::__std::option::Option::Some( + ::fayalite::bundle::TypeHint::<<#value_ty as ::fayalite::expr::ToExpr>::Type>::intern_dyn(), + ), + } + }); + } else { + from_canonical_type_variant_lets.push(quote! { + #variant_var.from_canonical_type_helper_no_value(#variant_name_str); + }); + enum_type_variants.push(quote! { + ::fayalite::enum_::VariantType { + name: ::fayalite::intern::Intern::intern(#variant_name_str), + ty: ::fayalite::__std::option::Option::None, + } + }); + enum_type_variants_hint.push(quote! { + ::fayalite::enum_::VariantType { + name: ::fayalite::intern::Intern::intern(#variant_name_str), + ty: ::fayalite::__std::option::Option::None, + } + }); + } + variant_vars.push(variant_var); + match_enum_arms.push(match &variant.value { + VariantValue::None => quote! { + #variant_index => #match_enum_ident::#variant_name, + }, + VariantValue::Direct { value_type } => quote! { + #variant_index => #match_enum_ident::#variant_name { + #(#match_enum_field_names)*: ::fayalite::expr::ToExpr::to_expr( + &__variant_access.downcast_unchecked::< + <#value_type as ::fayalite::expr::ToExpr>::Type>(), + ), + }, + }, + VariantValue::Struct { + value_struct: ItemStruct { ident, .. }, + .. + } => quote! { + #variant_index => { + let __variant_access = ::fayalite::expr::ToExpr::to_expr( + &__variant_access.downcast_unchecked::< + <#ident #fixed_type_type_generics as ::fayalite::expr::ToExpr>::Type, + >(), + ); + #match_enum_ident::#variant_name { + #(#match_enum_field_names: (*__variant_access).#match_enum_field_names,)* + } + }, + }, + }); + let builder_field_and_types = Vec::from_iter(variant.fields.iter().map( + |ParsedField { + options, + vis: _, + name, + ty, + }| { + let FieldOptions {} = options.body; + (name, ty) + }, + )); + let builder_field_vars = Vec::from_iter( + builder_field_and_types + .iter() + .map(|(name, _)| &builder.get_field(name).unwrap().1.builder_field_name), + ); + let build_body = match &variant.value { + VariantValue::None => parse_quote! { + { + ::fayalite::expr::ToExpr::to_expr( + &::fayalite::expr::ops::EnumLiteral::<#type_struct_ident #fixed_type_type_generics>::new_unchecked( + ::fayalite::__std::option::Option::None, + #variant_index, + ::fayalite::ty::FixedType::fixed_type(), + ), + ) + } + }, + VariantValue::Direct { value_type: _ } => parse_quote! { + { + ::fayalite::expr::ToExpr::to_expr( + &::fayalite::expr::ops::EnumLiteral::<#type_struct_ident #fixed_type_type_generics>::new_unchecked( + ::fayalite::__std::option::Option::Some(#(#builder_field_vars)*.to_canonical_dyn()), + #variant_index, + ::fayalite::ty::FixedType::fixed_type(), + ), + ) + } + }, + VariantValue::Struct { + parsed_struct: + ParsedStruct { + names: + ParsedStructNames { + type_struct_ident: field_type_struct_ident, + .. + }, + .. + }, + .. + } => parse_quote! { + { + let __builder = <#field_type_struct_ident #fixed_type_type_generics as ::fayalite::bundle::BundleType>::builder(); + #(let __builder = __builder.#builder_field_vars(#builder_field_vars);)* + ::fayalite::expr::ToExpr::to_expr( + &::fayalite::expr::ops::EnumLiteral::<#type_struct_ident #fixed_type_type_generics>::new_unchecked( + ::fayalite::__std::option::Option::Some(__builder.build().to_canonical_dyn()), + #variant_index, + ::fayalite::ty::FixedType::fixed_type(), + ), + ) + } + }, + }; + builder + .make_build_method( + &format_ident!("variant_{}", variant_name), + variant.fields.iter().map( + |ParsedField { + options, + vis: _, + name, + ty, + }| { + let FieldOptions {} = options.body; + (name.clone(), parse_quote! { ::fayalite::expr::Expr<#ty> }) + }, + ), + &fixed_type_generics, + &parse_quote! {#type_struct_ident #fixed_type_type_generics}, + &parse_quote! { ::fayalite::expr::Expr<#target #fixed_type_type_generics> }, + build_body, + ) + .to_tokens(tokens); + } + let match_enum = ItemEnum { + attrs: vec![parse_quote! {#[allow(non_camel_case_types)]}], + vis: vis.clone(), + enum_token: *enum_token, + ident: match_enum_ident, + generics: fixed_type_generics.clone(), + brace_token: *brace_token, + variants: match_enum_variants, + }; + let match_enum_ident = &match_enum.ident; + match_enum.to_tokens(tokens); + make_connect_impl( + *connect_inexact, + &fixed_type_generics, + type_struct_ident, + variants.iter().flat_map(|variant| { + variant.fields.iter().map(|field| { + let ty = &field.ty; + parse_quote_spanned! {field.name.span()=> + <#ty as ::fayalite::expr::ToExpr>::Type + } + }) + }), + ) + .to_tokens(tokens); + let variant_count = variants.len(); + let empty_builder_ty = builder.ty([], Some(&parse_quote! { Self }), false); + quote! { + #[automatically_derived] + impl #fixed_type_impl_generics ::fayalite::__std::fmt::Debug for #match_enum_ident #fixed_type_type_generics + #fixed_type_where_clause + { + fn fmt(&self, f: &mut ::fayalite::__std::fmt::Formatter<'_>) -> ::fayalite::__std::fmt::Result { + match *self { + #(#match_enum_debug_arms)* + } + } + } + + #[automatically_derived] + impl #fixed_type_impl_generics ::fayalite::ty::FixedType for #type_struct_ident #fixed_type_type_generics + #fixed_type_where_clause + { + fn fixed_type() -> Self { + Self { + #(#non_empty_variant_names: ::fayalite::ty::FixedType::fixed_type(),)* + } + } + } + + fn __check_field() + where + ::Type: ::fayalite::ty::Type, + {} + fn __check_fields #fixed_type_impl_generics(_: #target #fixed_type_type_generics) + #fixed_type_where_clause + { + #(#field_checks)* + } + + #[automatically_derived] + impl #fixed_type_impl_generics ::fayalite::__std::fmt::Debug for #type_struct_ident #fixed_type_type_generics + #fixed_type_where_clause + { + fn fmt(&self, f: &mut ::fayalite::__std::fmt::Formatter<'_>) -> ::fayalite::__std::fmt::Result { + #debug_type_body + } + } + + #[automatically_derived] + impl #fixed_type_impl_generics ::fayalite::ty::Type for #type_struct_ident #fixed_type_type_generics + #fixed_type_where_clause + { + type CanonicalType = ::fayalite::enum_::DynEnumType; + type Value = #target #fixed_type_type_generics; + type CanonicalValue = ::fayalite::enum_::DynEnum; + type MaskType = ::fayalite::int::UIntType<1>; + type MaskValue = ::fayalite::int::UInt<1>; + type MatchVariant = #match_enum_ident #fixed_type_type_generics; + type MatchActiveScope = ::fayalite::module::Scope; + type MatchVariantAndInactiveScope = ::fayalite::enum_::EnumMatchVariantAndInactiveScope; + type MatchVariantsIter = ::fayalite::enum_::EnumMatchVariantsIter; + fn match_variants( + this: ::fayalite::expr::Expr<::Value>, + module_builder: &mut ::fayalite::module::ModuleBuilder, + source_location: ::fayalite::source_location::SourceLocation, + ) -> ::MatchVariantsIter + where + ::Type: ::fayalite::bundle::BundleType, + { + module_builder.enum_match_variants_helper(this, source_location) + } + fn mask_type(&self) -> ::MaskType { + ::fayalite::int::UIntType::new() + } + fn canonical(&self) -> ::CanonicalType { + let variants = ::fayalite::enum_::EnumType::variants(self); + ::fayalite::enum_::DynEnumType::new(variants) + } + fn source_location(&self) -> ::fayalite::source_location::SourceLocation { + ::fayalite::source_location::SourceLocation::caller() + } + fn type_enum(&self) -> ::fayalite::ty::TypeEnum { + ::fayalite::ty::TypeEnum::EnumType(::fayalite::ty::Type::canonical(self)) + } + #[allow(non_snake_case)] + fn from_canonical_type(t: ::CanonicalType) -> Self { + let [#(#variant_vars),*] = *::fayalite::enum_::EnumType::variants(&t) else { + ::fayalite::__std::panic!("wrong number of variants"); + }; + #(#from_canonical_type_variant_lets)* + Self { + #(#non_empty_variant_names: #non_empty_variant_vars,)* + } + } + } + + #[automatically_derived] + #[allow(clippy::init_numbered_fields)] + impl #fixed_type_impl_generics ::fayalite::enum_::EnumType for #type_struct_ident #fixed_type_type_generics + #fixed_type_where_clause + { + type Builder = #empty_builder_ty; + fn match_activate_scope( + v: ::MatchVariantAndInactiveScope, + ) -> (::MatchVariant, ::MatchActiveScope) { + let (__variant_access, __scope) = v.activate(); + ( + match ::fayalite::expr::ops::VariantAccess::variant_index(&*__variant_access) { + #(#match_enum_arms)* + #variant_count.. => ::fayalite::__std::panic!("invalid variant index"), + }, + __scope, + ) + } + fn builder() -> ::Builder { + #empty_builder_ty::new() + } + fn variants(&self) -> ::fayalite::intern::Interned<[::fayalite::enum_::VariantType<::fayalite::intern::Interned>]> { + ::fayalite::intern::Intern::intern(&[#(#enum_type_variants,)*][..]) + } + fn variants_hint() -> ::fayalite::enum_::VariantsHint { + ::fayalite::enum_::VariantsHint::new([#(#enum_type_variants_hint,)*], false) + } + } + + #[automatically_derived] + impl #fixed_type_impl_generics ::fayalite::expr::ToExpr for #target #fixed_type_type_generics + #fixed_type_where_clause + { + type Type = #type_struct_ident #fixed_type_type_generics; + fn ty(&self) -> ::Type { + ::fayalite::ty::FixedType::fixed_type() + } + fn to_expr(&self) -> ::fayalite::expr::Expr { + ::fayalite::expr::Expr::from_value(self) + } + } + + #[automatically_derived] + impl #fixed_type_impl_generics ::fayalite::ty::Value for #target #fixed_type_type_generics + #fixed_type_where_clause + { + fn to_canonical(&self) -> <::Type as ::fayalite::ty::Type>::CanonicalValue { + let __ty = ::fayalite::ty::Type::canonical(&::fayalite::expr::ToExpr::ty(self)); + match self { + #(Self::#variant_names { #variant_field_pats } => { + ::fayalite::enum_::DynEnum::new_by_name( + __ty, + ::fayalite::intern::Intern::intern(#variant_name_strs), + #variant_to_canonical_values, + ) + })* + } + } + } + + #[automatically_derived] + impl #fixed_type_impl_generics ::fayalite::enum_::EnumValue for #target #fixed_type_type_generics + #fixed_type_where_clause + { + } + } + .to_tokens(tokens); + } +} + +pub(crate) fn value_derive_enum(item: ItemEnum) -> syn::Result { + let item = ParsedEnum::parse(item)?; + let outline_generated = item.options.body.outline_generated; + let mut contents = quote! { + const _: () = { + #item + }; + }; + if outline_generated.is_some() { + contents = crate::outline_generated(contents, "value-enum-"); + } + Ok(contents) +} diff --git a/crates/fayalite-proc-macros-impl/src/value_derive_struct.rs b/crates/fayalite-proc-macros-impl/src/value_derive_struct.rs new file mode 100644 index 0000000..472e984 --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/value_derive_struct.rs @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + value_derive_common::{ + append_field, derive_clone_hash_eq_partialeq_for_struct, get_target, make_connect_impl, + Bounds, Builder, FieldsKind, ParsedField, ValueDeriveGenerics, + }, + Errors, HdlAttr, +}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, quote_spanned, ToTokens}; +use syn::{ + parse_quote, parse_quote_spanned, spanned::Spanned, FieldMutability, Generics, Ident, + ItemStruct, Member, Path, Token, Visibility, +}; + +crate::options! { + #[options = StructOptions] + pub(crate) enum StructOption { + OutlineGenerated(outline_generated), + FixedType(fixed_type), + ConnectInexact(connect_inexact), + Bounds(where_, Bounds), + Target(target, Path), + } +} + +crate::options! { + #[options = FieldOptions] + pub(crate) enum FieldOption { + Flip(flip), + Skip(skip), + } +} + +pub(crate) struct ParsedStructNames { + pub(crate) ident: Ident, + pub(crate) type_struct_debug_ident: S, + pub(crate) type_struct_ident: Ident, + pub(crate) match_variant_ident: I, + pub(crate) builder_struct_ident: I, + pub(crate) mask_match_variant_ident: I, + pub(crate) mask_type_ident: I, + pub(crate) mask_type_debug_ident: S, + pub(crate) mask_value_ident: I, + pub(crate) mask_value_debug_ident: S, + pub(crate) mask_builder_struct_ident: I, +} + +pub(crate) struct ParsedStruct { + pub(crate) options: HdlAttr, + pub(crate) vis: Visibility, + pub(crate) struct_token: Token![struct], + pub(crate) generics: Generics, + pub(crate) fields_kind: FieldsKind, + pub(crate) fields: Vec>, + pub(crate) semi_token: Option, + pub(crate) skip_check_fields: bool, + pub(crate) names: ParsedStructNames, Option>, +} + +impl ParsedStruct { + pub(crate) fn parse(item: &mut ItemStruct) -> syn::Result { + let ItemStruct { + attrs, + vis, + struct_token, + ident, + generics, + fields, + semi_token, + } = item; + let mut errors = Errors::new(); + let struct_options = errors + .unwrap_or_default(HdlAttr::parse_and_take_attr(attrs)) + .unwrap_or_default(); + let (fields_kind, fields) = ParsedField::parse_fields(&mut errors, fields, false); + errors.finish()?; + Ok(ParsedStruct { + options: struct_options, + vis: vis.clone(), + struct_token: *struct_token, + generics: generics.clone(), + fields_kind, + fields, + semi_token: *semi_token, + skip_check_fields: false, + names: ParsedStructNames { + ident: ident.clone(), + type_struct_debug_ident: None, + type_struct_ident: format_ident!("__{}__Type", ident), + match_variant_ident: None, + builder_struct_ident: None, + mask_match_variant_ident: None, + mask_type_ident: None, + mask_type_debug_ident: None, + mask_value_ident: None, + mask_value_debug_ident: None, + mask_builder_struct_ident: None, + }, + }) + } + pub(crate) fn write_body( + &self, + target: Path, + names: ParsedStructNames<&Ident, &String>, + is_for_mask: bool, + tokens: &mut TokenStream, + ) { + let Self { + options, + vis, + struct_token, + generics, + fields_kind, + fields, + semi_token, + skip_check_fields, + names: _, + } = self; + let skip_check_fields = *skip_check_fields || is_for_mask; + let ParsedStructNames { + ident: struct_ident, + type_struct_debug_ident, + type_struct_ident, + match_variant_ident, + builder_struct_ident, + mask_match_variant_ident: _, + mask_type_ident, + mask_type_debug_ident: _, + mask_value_ident, + mask_value_debug_ident, + mask_builder_struct_ident: _, + } = names; + let StructOptions { + outline_generated: _, + where_, + target: _, + fixed_type, + connect_inexact, + } = &options.body; + let ValueDeriveGenerics { + generics, + fixed_type_generics, + } = ValueDeriveGenerics::get(generics.clone(), where_); + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + let unskipped_fields = fields + .iter() + .filter(|field| field.options.body.skip.is_none()); + let _field_names = Vec::from_iter(fields.iter().map(|field| field.name.clone())); + let unskipped_field_names = + Vec::from_iter(unskipped_fields.clone().map(|field| field.name.clone())); + let unskipped_field_name_strs = Vec::from_iter( + unskipped_field_names + .iter() + .map(|field_name| field_name.to_token_stream().to_string()), + ); + let unskipped_field_vars = Vec::from_iter( + unskipped_field_names + .iter() + .map(|field_name| format_ident!("__v_{}", field_name)), + ); + let unskipped_field_flips = Vec::from_iter( + unskipped_fields + .clone() + .map(|field| field.options.body.flip.is_some()), + ); + let mut any_fields_skipped = false; + let type_fields = Vec::from_iter(fields.iter().filter_map(|field| { + let ParsedField { + options, + vis, + name, + ty, + } = field; + let FieldOptions { flip: _, skip } = &options.body; + if skip.is_some() { + any_fields_skipped = true; + return None; + } + let ty = if is_for_mask { + parse_quote! { ::fayalite::ty::AsMask<#ty> } + } else { + ty.to_token_stream() + }; + Some(syn::Field { + attrs: vec![], + vis: vis.clone(), + mutability: FieldMutability::None, + ident: match name.clone() { + Member::Named(name) => Some(name), + Member::Unnamed(_) => None, + }, + colon_token: None, + ty: parse_quote! { <#ty as ::fayalite::expr::ToExpr>::Type }, + }) + })); + let field_types = Vec::from_iter(type_fields.iter().map(|field| field.ty.clone())); + let match_variant_fields = Vec::from_iter(fields.iter().zip(&type_fields).map( + |(parsed_field, type_field)| { + let field_ty = &parsed_field.ty; + syn::Field { + ty: parse_quote! { ::fayalite::expr::Expr<#field_ty> }, + ..type_field.clone() + } + }, + )); + + let mask_value_fields = Vec::from_iter(fields.iter().zip(&type_fields).map( + |(parsed_field, type_field)| { + let field_ty = &parsed_field.ty; + syn::Field { + ty: parse_quote! { ::fayalite::ty::AsMask<#field_ty> }, + ..type_field.clone() + } + }, + )); + + let mut type_struct_fields = fields_kind.into_fields(type_fields); + let mut match_variant_fields = fields_kind.into_fields(match_variant_fields); + let mut mask_value_fields = fields_kind.into_fields(mask_value_fields); + let phantom_data_field_name = any_fields_skipped.then(|| { + let phantom_data_field_name = Ident::new("__phantom_data", type_struct_ident.span()); + let member = append_field( + &mut type_struct_fields, + syn::Field { + attrs: vec![], + vis: vis.clone(), + mutability: FieldMutability::None, + ident: Some(phantom_data_field_name.clone()), + colon_token: None, + ty: parse_quote_spanned! {type_struct_ident.span()=> + ::fayalite::__std::marker::PhantomData<#struct_ident #type_generics> + }, + }, + ); + append_field( + &mut match_variant_fields, + syn::Field { + attrs: vec![], + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: Some(phantom_data_field_name.clone()), + colon_token: None, + ty: parse_quote_spanned! {type_struct_ident.span()=> + ::fayalite::__std::marker::PhantomData<#struct_ident #type_generics> + }, + }, + ); + append_field( + &mut mask_value_fields, + syn::Field { + attrs: vec![], + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: Some(phantom_data_field_name), + colon_token: None, + ty: parse_quote_spanned! {type_struct_ident.span()=> + ::fayalite::__std::marker::PhantomData<#struct_ident #type_generics> + }, + }, + ); + member + }); + let phantom_data_field_name_slice = phantom_data_field_name.as_slice(); + let type_struct = ItemStruct { + attrs: vec![parse_quote! {#[allow(non_camel_case_types)]}], + vis: vis.clone(), + struct_token: *struct_token, + ident: type_struct_ident.clone(), + generics: generics.clone(), + fields: type_struct_fields, + semi_token: *semi_token, + }; + type_struct.to_tokens(tokens); + let match_variant_struct = ItemStruct { + attrs: vec![parse_quote! {#[allow(non_camel_case_types)]}], + vis: vis.clone(), + struct_token: *struct_token, + ident: match_variant_ident.clone(), + generics: generics.clone(), + fields: match_variant_fields, + semi_token: *semi_token, + }; + match_variant_struct.to_tokens(tokens); + let mask_type_body = if is_for_mask { + quote! { + ::fayalite::__std::clone::Clone::clone(self) + } + } else { + let mask_value_struct = ItemStruct { + attrs: vec![parse_quote! {#[allow(non_camel_case_types)]}], + vis: vis.clone(), + struct_token: *struct_token, + ident: mask_value_ident.clone(), + generics: generics.clone(), + fields: mask_value_fields, + semi_token: *semi_token, + }; + mask_value_struct.to_tokens(tokens); + let debug_mask_value_body = match fields_kind { + FieldsKind::Unit => quote! { + f.debug_struct(#mask_value_debug_ident).finish() + }, + FieldsKind::Named(_) => quote! { + f.debug_struct(#mask_value_debug_ident)#(.field(#unskipped_field_name_strs, &self.#unskipped_field_names))*.finish() + }, + FieldsKind::Unnamed(_) => quote! { + f.debug_tuple(#mask_value_debug_ident)#(.field(&self.#unskipped_field_names))*.finish() + }, + }; + quote! { + #[automatically_derived] + impl #impl_generics ::fayalite::__std::fmt::Debug for #mask_value_ident #type_generics + #where_clause + { + fn fmt(&self, f: &mut ::fayalite::__std::fmt::Formatter<'_>) -> ::fayalite::__std::fmt::Result { + #debug_mask_value_body + } + } + }.to_tokens(tokens); + quote! { + #mask_type_ident { + #(#unskipped_field_names: ::fayalite::ty::Type::mask_type(&self.#unskipped_field_names),)* + #(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)* + } + } + }; + let debug_type_body = match fields_kind { + FieldsKind::Unit => quote! { + f.debug_struct(#type_struct_debug_ident).finish() + }, + FieldsKind::Named(_) => quote! { + f.debug_struct(#type_struct_debug_ident)#(.field(#unskipped_field_name_strs, &self.#unskipped_field_names))*.finish() + }, + FieldsKind::Unnamed(_) => quote! { + f.debug_tuple(#type_struct_debug_ident)#(.field(&self.#unskipped_field_names))*.finish() + }, + }; + for the_struct_ident in [&type_struct_ident, match_variant_ident] + .into_iter() + .chain(is_for_mask.then_some(mask_value_ident)) + { + derive_clone_hash_eq_partialeq_for_struct( + the_struct_ident, + &generics, + &Vec::from_iter( + unskipped_field_names + .iter() + .cloned() + .chain(phantom_data_field_name.clone()), + ), + ) + .to_tokens(tokens); + } + let check_v = format_ident!("__v"); + let field_checks = Vec::from_iter(fields.iter().map(|ParsedField { ty, name, .. }| { + quote_spanned! {ty.span()=> + __check_field(#check_v.#name); + } + })); + if fixed_type.is_some() { + let (impl_generics, type_generics, where_clause) = fixed_type_generics.split_for_impl(); + quote! { + #[automatically_derived] + impl #impl_generics ::fayalite::ty::FixedType for #type_struct_ident #type_generics + #where_clause + { + fn fixed_type() -> Self { + Self { + #(#unskipped_field_names: ::fayalite::ty::FixedType::fixed_type(),)* + #(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)* + } + } + } + } + .to_tokens(tokens); + } + if !skip_check_fields { + quote! { + fn __check_field(_v: T) + where + ::Type: ::fayalite::ty::Type, + {} + fn __check_fields #impl_generics(#check_v: #target #type_generics) + #where_clause + { + #(#field_checks)* + } + } + .to_tokens(tokens); + } + let mut builder = Builder::new(builder_struct_ident.clone(), vis.clone()); + for field in unskipped_fields.clone() { + builder.insert_field( + field.name.clone(), + |v| { + parse_quote_spanned! {v.span()=> + ::fayalite::expr::ToExpr::to_expr(&#v) + } + }, + |t| { + parse_quote_spanned! {t.span()=> + ::fayalite::expr::Expr<<<#t as ::fayalite::expr::ToExpr>::Type as ::fayalite::ty::Type>::Value> + } + }, + |t| { + parse_quote_spanned! {t.span()=> + where + #t: ::fayalite::expr::ToExpr, + } + }, + ); + } + let builder = builder.finish_filling_in_fields(); + builder.to_tokens(tokens); + let build_type_fields = + Vec::from_iter(unskipped_fields.clone().map(|ParsedField { name, .. }| { + let builder_field_name = &builder.get_field(name).unwrap().1.builder_field_name; + quote_spanned! {struct_ident.span()=> + #name: ::fayalite::expr::ToExpr::ty(&#builder_field_name) + } + })); + let build_expr_fields = + Vec::from_iter(unskipped_fields.clone().map(|ParsedField { name, .. }| { + let builder_field_name = &builder.get_field(name).unwrap().1.builder_field_name; + quote_spanned! {struct_ident.span()=> + #builder_field_name.to_canonical_dyn() + } + })); + let build_specified_fields = unskipped_fields.clone().map( + |ParsedField { + options: _, + vis: _, + name, + ty, + }| { + let ty = if is_for_mask { + parse_quote_spanned! {name.span()=> + ::fayalite::expr::Expr<::fayalite::ty::AsMask<#ty>> + } + } else { + parse_quote_spanned! {name.span()=> + ::fayalite::expr::Expr<#ty> + } + }; + (name.clone(), ty) + }, + ); + let build_body = parse_quote_spanned! {struct_ident.span()=> + { + ::fayalite::expr::ToExpr::to_expr( + &::fayalite::expr::ops::BundleLiteral::new_unchecked( + ::fayalite::intern::Intern::intern(&[#( + #build_expr_fields, + )*][..]), + #type_struct_ident { + #(#build_type_fields,)* + #(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)* + }, + ), + ) + } + }; + builder + .make_build_method( + &Ident::new("build", struct_ident.span()), + build_specified_fields, + &generics, + &parse_quote_spanned! {struct_ident.span()=> + #type_struct_ident #type_generics + }, + &parse_quote_spanned! {struct_ident.span()=> + ::fayalite::expr::Expr<#target #type_generics> + }, + build_body, + ) + .to_tokens(tokens); + make_connect_impl( + *connect_inexact, + &generics, + &type_struct_ident, + unskipped_fields.clone().map(|field| { + let ty = &field.ty; + parse_quote_spanned! {field.name.span()=> + <#ty as ::fayalite::expr::ToExpr>::Type + } + }), + ) + .to_tokens(tokens); + let empty_builder_ty = builder.ty([], Some(&parse_quote! { Self }), false); + quote! { + #[automatically_derived] + impl #impl_generics ::fayalite::__std::fmt::Debug for #type_struct_ident #type_generics + #where_clause + { + fn fmt(&self, f: &mut ::fayalite::__std::fmt::Formatter<'_>) -> ::fayalite::__std::fmt::Result { + #debug_type_body + } + } + + #[automatically_derived] + impl #impl_generics ::fayalite::ty::Type for #type_struct_ident #type_generics + #where_clause + { + type CanonicalType = ::fayalite::bundle::DynBundleType; + type Value = #target #type_generics; + type CanonicalValue = ::fayalite::bundle::DynBundle; + type MaskType = #mask_type_ident #type_generics; + type MaskValue = #mask_value_ident #type_generics; + type MatchVariant = #match_variant_ident #type_generics; + type MatchActiveScope = (); + type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope<#match_variant_ident #type_generics>; + type MatchVariantsIter = ::fayalite::__std::iter::Once<::MatchVariantAndInactiveScope>; + #[allow(unused_variables)] + fn match_variants( + this: ::fayalite::expr::Expr<::Value>, + module_builder: &mut ::fayalite::module::ModuleBuilder, + source_location: ::fayalite::source_location::SourceLocation, + ) -> ::MatchVariantsIter + where + ::Type: ::fayalite::bundle::BundleType, + { + ::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(#match_variant_ident { + #(#unskipped_field_names: this.field(#unskipped_field_name_strs),)* + #(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)* + })) + } + fn mask_type(&self) -> ::MaskType { + #mask_type_body + } + fn canonical(&self) -> ::CanonicalType { + let fields = ::fayalite::bundle::BundleType::fields(self); + ::fayalite::bundle::DynBundleType::new(fields) + } + fn source_location(&self) -> ::fayalite::source_location::SourceLocation { + ::fayalite::source_location::SourceLocation::caller() + } + fn type_enum(&self) -> ::fayalite::ty::TypeEnum { + ::fayalite::ty::TypeEnum::BundleType(::fayalite::ty::Type::canonical(self)) + } + fn from_canonical_type(t: ::CanonicalType) -> Self { + let [#(#unskipped_field_vars),*] = *::fayalite::bundle::BundleType::fields(&t) else { + ::fayalite::__std::panic!("wrong number of fields"); + }; + Self { + #(#unskipped_field_names: #unskipped_field_vars.from_canonical_type_helper(#unskipped_field_name_strs, #unskipped_field_flips),)* + #(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)* + } + } + } + + #[automatically_derived] + impl #impl_generics ::fayalite::ty::TypeWithDeref for #type_struct_ident #type_generics + #where_clause + { + #[allow(unused_variables)] + fn expr_deref(this: &::fayalite::expr::Expr<::Value>) -> &::MatchVariant { + ::fayalite::intern::Interned::<_>::into_inner(::fayalite::intern::Intern::intern_sized( + #match_variant_ident { + #(#unskipped_field_names: this.field(#unskipped_field_name_strs),)* + #(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)* + } + )) + } + } + + #[automatically_derived] + impl #impl_generics ::fayalite::bundle::BundleType for #type_struct_ident #type_generics + #where_clause + { + type Builder = #empty_builder_ty; + fn builder() -> ::Builder { + #empty_builder_ty::new() + } + fn fields(&self) -> ::fayalite::intern::Interned<[::fayalite::bundle::FieldType<::fayalite::intern::Interned>]> { + ::fayalite::intern::Intern::intern(&[#( + ::fayalite::bundle::FieldType { + name: ::fayalite::intern::Intern::intern(#unskipped_field_name_strs), + flipped: #unskipped_field_flips, + ty: ::fayalite::ty::DynType::canonical_dyn(&self.#unskipped_field_names), + }, + )*][..]) + } + fn fields_hint() -> ::fayalite::bundle::FieldsHint { + ::fayalite::bundle::FieldsHint::new([#( + ::fayalite::bundle::FieldType { + name: ::fayalite::intern::Intern::intern(#unskipped_field_name_strs), + flipped: #unskipped_field_flips, + ty: ::fayalite::bundle::TypeHint::<#field_types>::intern_dyn(), + }, + )*], false) + } + } + + #[automatically_derived] + impl #impl_generics ::fayalite::expr::ToExpr for #target #type_generics + #where_clause + { + type Type = #type_struct_ident #type_generics; + fn ty(&self) -> ::Type { + #type_struct_ident { + #(#unskipped_field_names: ::fayalite::expr::ToExpr::ty(&self.#unskipped_field_names),)* + #(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)* + } + } + fn to_expr(&self) -> ::fayalite::expr::Expr { + ::fayalite::expr::Expr::from_value(self) + } + } + + #[automatically_derived] + impl #impl_generics ::fayalite::ty::Value for #target #type_generics + #where_clause + { + fn to_canonical(&self) -> <::Type as ::fayalite::ty::Type>::CanonicalValue { + let ty = ::fayalite::ty::Type::canonical(&::fayalite::expr::ToExpr::ty(self)); + ::fayalite::bundle::DynBundle::new(ty, ::fayalite::__std::sync::Arc::new([ + #(::fayalite::ty::DynValueTrait::to_canonical_dyn(&self.#unskipped_field_names),)* + ])) + } + } + + #[automatically_derived] + impl #impl_generics ::fayalite::bundle::BundleValue for #target #type_generics + #where_clause + { + } + } + .to_tokens(tokens); + } +} + +impl ToTokens for ParsedStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let ParsedStructNames { + ident: struct_ident, + type_struct_debug_ident, + type_struct_ident, + match_variant_ident, + builder_struct_ident, + mask_match_variant_ident, + mask_type_ident, + mask_type_debug_ident, + mask_value_ident, + mask_value_debug_ident, + mask_builder_struct_ident, + } = &self.names; + macro_rules! unwrap_or_set { + ($(let $var:ident =? $fallback_value:expr;)*) => { + $(let $var = $var.clone().unwrap_or_else(|| $fallback_value);)* + }; + } + unwrap_or_set! { + let type_struct_debug_ident =? format!("{struct_ident}::Type"); + let match_variant_ident =? format_ident!("__{}__MatchVariant", struct_ident); + let builder_struct_ident =? format_ident!("__{}__Builder", struct_ident); + let mask_match_variant_ident =? format_ident!("__AsMask__{}__MatchVariant", struct_ident); + let mask_type_ident =? format_ident!("__AsMask__{}__Type", struct_ident); + let mask_type_debug_ident =? format!("AsMask<{struct_ident}>::Type"); + let mask_value_ident =? format_ident!("__AsMask__{}", struct_ident); + let mask_value_debug_ident =? format!("AsMask<{struct_ident}>"); + let mask_builder_struct_ident =? format_ident!("__AsMask__{}__Builder", struct_ident); + } + let target = get_target(&self.options.body.target, struct_ident); + let names = ParsedStructNames { + ident: struct_ident.clone(), + type_struct_debug_ident: &type_struct_debug_ident, + type_struct_ident: type_struct_ident.clone(), + match_variant_ident: &match_variant_ident, + builder_struct_ident: &builder_struct_ident, + mask_match_variant_ident: &mask_match_variant_ident, + mask_type_ident: &mask_type_ident, + mask_type_debug_ident: &mask_type_debug_ident, + mask_value_ident: &mask_value_ident, + mask_value_debug_ident: &mask_value_debug_ident, + mask_builder_struct_ident: &mask_builder_struct_ident, + }; + self.write_body(target, names, false, tokens); + let mask_names = ParsedStructNames { + ident: mask_value_ident.clone(), + type_struct_debug_ident: &mask_type_debug_ident, + type_struct_ident: mask_type_ident.clone(), + match_variant_ident: &mask_match_variant_ident, + builder_struct_ident: &mask_builder_struct_ident, + mask_match_variant_ident: &mask_match_variant_ident, + mask_type_ident: &mask_type_ident, + mask_type_debug_ident: &mask_type_debug_ident, + mask_value_ident: &mask_value_ident, + mask_value_debug_ident: &mask_value_debug_ident, + mask_builder_struct_ident: &mask_builder_struct_ident, + }; + self.write_body(mask_value_ident.clone().into(), mask_names, true, tokens); + } +} + +pub(crate) fn value_derive_struct(mut item: ItemStruct) -> syn::Result { + let item = ParsedStruct::parse(&mut item)?; + let outline_generated = item.options.body.outline_generated; + let mut contents = quote! { + const _: () = { + #item + }; + }; + if outline_generated.is_some() { + contents = crate::outline_generated(contents, "value-struct-"); + } + Ok(contents) +} diff --git a/crates/fayalite-proc-macros/Cargo.toml b/crates/fayalite-proc-macros/Cargo.toml new file mode 100644 index 0000000..8e09bd1 --- /dev/null +++ b/crates/fayalite-proc-macros/Cargo.toml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# See Notices.txt for copyright information +[package] +name = "fayalite-proc-macros" +version = "0.1.0" +edition = "2021" +workspace = "../.." +license = "LGPL-3.0-or-later" + +[lib] +proc-macro = true + +[dependencies] +fayalite-proc-macros-impl = { version = "=0.1.0", path = "../fayalite-proc-macros-impl" } diff --git a/crates/fayalite-proc-macros/src/lib.rs b/crates/fayalite-proc-macros/src/lib.rs new file mode 100644 index 0000000..d40fb19 --- /dev/null +++ b/crates/fayalite-proc-macros/src/lib.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +#[proc_macro_attribute] +pub fn hdl_module( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + match fayalite_proc_macros_impl::module(attr.into(), item.into()) { + Ok(retval) => retval.into(), + Err(err) => err.into_compile_error().into(), + } +} + +#[proc_macro_derive(Value, attributes(hdl))] +pub fn value_derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + match fayalite_proc_macros_impl::value_derive(item.into()) { + Ok(retval) => retval.into(), + Err(err) => err.into_compile_error().into(), + } +} diff --git a/crates/fayalite-visit-gen/Cargo.toml b/crates/fayalite-visit-gen/Cargo.toml new file mode 100644 index 0000000..3360f00 --- /dev/null +++ b/crates/fayalite-visit-gen/Cargo.toml @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# See Notices.txt for copyright information +[package] +name = "fayalite-visit-gen" +version = "0.1.0" +edition = "2021" +workspace = "../.." +license = "LGPL-3.0-or-later" + +[dependencies] +indexmap = { version = "2.2.6", features = ["serde"] } +prettyplease = "0.2.20" +proc-macro2 = "1.0.83" +quote = "1.0.36" +serde = { version = "1.0.202", features = ["derive"] } +serde_json = { version = "1.0.117", features = ["preserve_order"] } +syn = { version = "2.0.66", features = ["full", "extra-traits"] } +thiserror = "1.0.61" diff --git a/crates/fayalite-visit-gen/src/ast.rs b/crates/fayalite-visit-gen/src/ast.rs new file mode 100644 index 0000000..9af462b --- /dev/null +++ b/crates/fayalite-visit-gen/src/ast.rs @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use indexmap::IndexMap; +use proc_macro2::{Span, TokenStream}; +use quote::{IdentFragment, ToTokens, TokenStreamExt}; +use serde::{Deserialize, Serialize}; +use std::{ + fmt::{self, Write}, + iter::FusedIterator, + str::FromStr, +}; +use thiserror::Error; + +macro_rules! impl_try_from_str { + ($ty:ty) => { + impl TryFrom<&'_ str> for $ty { + type Error = ::Err; + + fn try_from(v: &str) -> Result { + v.parse() + } + } + + impl TryFrom for $ty { + type Error = ::Err; + + fn try_from(v: String) -> Result { + v.parse() + } + } + }; +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +#[serde(into = "String", try_from = "String")] +pub struct Ident(pub String); + +impl ToTokens for Ident { + fn to_tokens(&self, tokens: &mut TokenStream) { + syn::Ident::from(self).to_tokens(tokens); + } +} + +impl IdentFragment for Ident { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&self.0) + } +} + +impl Ident { + pub fn is_start_char(ch: char) -> bool { + ch == '_' || ch.is_ascii_alphabetic() + } + pub fn is_continue_char(ch: char) -> bool { + ch == '_' || ch.is_ascii_alphanumeric() + } + pub fn is_ident(v: &str) -> bool { + !v.is_empty() + && v.starts_with(Self::is_start_char) + && v.trim_start_matches(Self::is_continue_char).is_empty() + } +} + +impl From for Path { + fn from(value: Ident) -> Self { + Path(value.0) + } +} + +impl From for String { + fn from(value: Ident) -> Self { + value.0 + } +} + +impl From for syn::Ident { + fn from(value: Ident) -> Self { + From::from(&value) + } +} + +impl From<&'_ Ident> for syn::Ident { + fn from(value: &Ident) -> Self { + syn::Ident::new(&value.0, Span::call_site()) + } +} + +#[derive(Clone, Debug, Error)] +#[error("invalid identifier")] +pub struct IdentParseError; + +impl_try_from_str!(Ident); + +impl FromStr for Ident { + type Err = IdentParseError; + + fn from_str(s: &str) -> Result { + if Self::is_ident(s) { + Ok(Self(s.into())) + } else { + Err(IdentParseError) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +#[serde(into = "String", try_from = "String")] + +pub struct Path(String); + +impl Path { + pub fn iter(&self) -> PathIter<'_> { + PathIter(&self.0) + } + pub fn last(&self) -> Ident { + self.iter().next_back().unwrap() + } + pub fn is_path(s: &str) -> bool { + if s.is_empty() { + false + } else { + s.split("::").all(Ident::is_ident) + } + } +} + +#[derive(Debug, Clone)] +pub struct PathIter<'a>(&'a str); + +impl Iterator for PathIter<'_> { + type Item = Ident; + + fn next(&mut self) -> Option { + if self.0.is_empty() { + None + } else if let Some((first, rest)) = self.0.split_once("::") { + self.0 = rest; + Some(Ident(first.into())) + } else { + let retval = self.0; + self.0 = &self.0[..0]; + Some(Ident(retval.into())) + } + } + + fn last(mut self) -> Option { + self.next_back() + } +} + +impl FusedIterator for PathIter<'_> {} + +impl DoubleEndedIterator for PathIter<'_> { + fn next_back(&mut self) -> Option { + if self.0.is_empty() { + None + } else if let Some((rest, last)) = self.0.rsplit_once("::") { + self.0 = rest; + Some(Ident(last.into())) + } else { + let retval = self.0; + self.0 = &self.0[..0]; + Some(Ident(retval.into())) + } + } +} + +impl ToTokens for Path { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_separated(self.iter(), ::default()); + } +} + +#[derive(Clone, Debug, Error)] +#[error("invalid path")] +pub struct PathParseError; + +impl From for String { + fn from(value: Path) -> Self { + value.0 + } +} + +impl_try_from_str!(Path); + +impl FromStr for Path { + type Err = PathParseError; + + fn from_str(value: &str) -> Result { + if value.is_empty() { + Err(PathParseError) + } else if value.split("::").all(Ident::is_ident) { + Ok(Self(value.into())) + } else { + Err(PathParseError) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Definitions { + pub types: std::collections::BTreeMap, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Definition { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fn_name_suffix: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub generics: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fold_where: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub visit_where: Option, + pub data: Data, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "$kind")] +pub enum Data { + ManualImpl, + Opaque, + Enum(Variants), + Struct(Fields), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +#[serde(into = "String", try_from = "String")] +pub struct FieldNameIdent { + pub ident: Ident, + pub is_getter: bool, +} + +impl FieldNameIdent { + pub fn to_member(&self) -> Option { + let Self { + ref ident, + is_getter, + } = *self; + if is_getter { + None + } else { + Some(syn::Ident::from(ident).into()) + } + } +} + +impl ToTokens for FieldNameIdent { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + ref ident, + is_getter, + } = *self; + ident.to_tokens(tokens); + if is_getter { + syn::token::Paren::default().surround(tokens, |_| {}); + } + } +} + +impl From for String { + fn from(value: FieldNameIdent) -> Self { + let mut retval = value.ident.0; + if value.is_getter { + retval.push_str("()"); + } + retval + } +} + +#[derive(Clone, Debug, Error)] +#[error("invalid field name")] +pub struct FieldNameParseError; + +impl_try_from_str!(FieldNameIdent); + +impl FromStr for FieldNameIdent { + type Err = FieldNameParseError; + + fn from_str(value: &str) -> Result { + let ident = value.strip_suffix("()"); + let is_getter = ident.is_some(); + let ident = ident.unwrap_or(value); + if let Ok(ident) = ident.parse() { + Ok(Self { ident, is_getter }) + } else { + Err(FieldNameParseError) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] +#[serde(into = "String", try_from = "String")] +pub struct WherePredicates(pub syn::punctuated::Punctuated); + +#[derive(Debug, Error)] +#[error("invalid `where` predicates")] +pub struct WherePredicatesParseError; + +impl_try_from_str!(WherePredicates); + +impl FromStr for WherePredicates { + type Err = WherePredicatesParseError; + + fn from_str(value: &str) -> Result { + Ok(Self( + syn::parse::Parser::parse_str(syn::punctuated::Punctuated::parse_terminated, value) + .map_err(|_| WherePredicatesParseError)?, + )) + } +} + +impl From for String { + fn from(value: WherePredicates) -> Self { + value.0.into_token_stream().to_string() + } +} + +impl From for syn::WhereClause { + fn from(value: WherePredicates) -> Self { + syn::WhereClause { + where_token: Default::default(), + predicates: value.0, + } + } +} + +impl From for WherePredicates { + fn from(value: syn::WhereClause) -> Self { + Self(value.predicates) + } +} + +impl ToTokens for WherePredicates { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0.to_tokens(tokens); + } +} + +#[derive(Serialize, Deserialize)] +#[serde(untagged)] +enum SerializedGenerics { + Where { + generics: String, + #[serde(rename = "where")] + where_predicates: WherePredicates, + }, + NoWhere(String), +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] +#[serde(into = "SerializedGenerics", try_from = "SerializedGenerics")] +pub struct Generics(pub syn::Generics); + +impl ToTokens for Generics { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0.to_tokens(tokens); + } +} + +impl From for SerializedGenerics { + fn from(mut value: Generics) -> Self { + match value.0.where_clause.take() { + Some(where_clause) => Self::Where { + generics: value.0.into_token_stream().to_string(), + where_predicates: where_clause.into(), + }, + None => Self::NoWhere(value.0.into_token_stream().to_string()), + } + } +} + +#[derive(Debug, Error)] +#[error("invalid generics")] +pub struct GenericsParseError; + +impl TryFrom for Generics { + type Error = GenericsParseError; + + fn try_from(value: SerializedGenerics) -> Result { + let (generics, where_clause) = match value { + SerializedGenerics::Where { + generics, + where_predicates, + } => (generics, Some(where_predicates.into())), + SerializedGenerics::NoWhere(generics) => (generics, None), + }; + let Ok(mut generics) = syn::parse_str::(&generics) else { + return Err(GenericsParseError); + }; + generics.where_clause = where_clause; + Ok(Self(generics)) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[serde(into = "String", try_from = "String")] +pub struct PathWithGenerics { + pub path: Path, + pub generics: Option, +} + +impl ToTokens for PathWithGenerics { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { path, generics } = self; + path.to_tokens(tokens); + if let Some(generics) = generics { + ::default().to_tokens(tokens); + generics.to_tokens(tokens); + } + } +} + +impl From for String { + fn from(value: PathWithGenerics) -> Self { + let PathWithGenerics { path, generics } = value; + let mut retval = String::from(path); + if let Some(generics) = generics { + write!(retval, "{}", generics.to_token_stream()).unwrap(); + } + retval + } +} + +#[derive(Clone, Debug, Error)] +#[error("invalid path with optional generics")] +pub struct PathWithGenericsParseError; + +impl_try_from_str!(PathWithGenerics); + +impl FromStr for PathWithGenerics { + type Err = PathWithGenericsParseError; + + fn from_str(value: &str) -> Result { + let (path, generics) = if let Some(lt_pos) = value.find('<') { + let (path, generics) = value.split_at(lt_pos); + let path = path.strip_suffix("::").unwrap_or(path); + match syn::parse_str(generics) { + Ok(generics) => (path, Some(generics)), + Err(_) => return Err(PathWithGenericsParseError), + } + } else { + (value, None) + }; + if let Ok(path) = path.parse() { + Ok(Self { path, generics }) + } else { + Err(PathWithGenericsParseError) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +#[serde(into = "String", try_from = "String")] +pub enum FieldName { + Index(usize), + Ident(FieldNameIdent), +} + +impl FieldName { + pub fn to_member(&self) -> Option { + match self { + &FieldName::Index(index) => Some(index.into()), + FieldName::Ident(ident) => ident.to_member(), + } + } +} + +impl ToTokens for FieldName { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + &FieldName::Index(index) => syn::Index::from(index).to_tokens(tokens), + FieldName::Ident(ident) => ident.to_tokens(tokens), + } + } +} + +impl From for String { + fn from(value: FieldName) -> Self { + match value { + FieldName::Index(index) => index.to_string(), + FieldName::Ident(ident) => ident.into(), + } + } +} + +impl_try_from_str!(FieldName); + +impl FromStr for FieldName { + type Err = FieldNameParseError; + + fn from_str(value: &str) -> Result { + if !value.is_empty() + && value + .trim_start_matches(|ch: char| ch.is_ascii_digit()) + .is_empty() + { + if let Ok(index) = value.parse() { + Ok(Self::Index(index)) + } else { + Err(FieldNameParseError) + } + } else { + value.parse().map(Self::Ident) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Fields { + #[serde( + default, + rename = "$constructor", + skip_serializing_if = "Option::is_none" + )] + pub constructor: Option, + #[serde(flatten)] + pub fields: IndexMap, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Variants { + pub variants: IndexMap>, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub enum Field { + Opaque, + Visible, + RefVisible, +} + +#[cfg(test)] +mod tests { + use crate::ast; + + #[test] + fn test_serialize() { + let definitions = ast::Definitions { + types: FromIterator::from_iter([ + ( + ast::Path("Module".into()), + ast::Definition { + fn_name_suffix: None, + generics: Some( + ast::SerializedGenerics::Where { + generics: "".into(), + where_predicates: "T::Type: BundleType," + .parse() + .unwrap(), + } + .try_into() + .unwrap(), + ), + fold_where: None, + visit_where: None, + data: ast::Data::Struct(ast::Fields { + constructor: Some("Module::new_unchecked".parse().unwrap()), + fields: FromIterator::from_iter([( + "name_id()".parse().unwrap(), + ast::Field::Visible, + )]), + }), + }, + ), + ( + ast::Path("NameId".into()), + ast::Definition { + fn_name_suffix: None, + generics: None, + fold_where: None, + visit_where: None, + data: ast::Data::Struct(ast::Fields { + constructor: None, + fields: FromIterator::from_iter([ + ("0".try_into().unwrap(), ast::Field::Opaque), + ("1".try_into().unwrap(), ast::Field::Opaque), + ]), + }), + }, + ), + ]), + }; + let definitions_str = serde_json::to_string_pretty(&definitions).unwrap(); + println!("{definitions_str}"); + assert_eq!( + definitions_str, + r#"{ + "types": { + "Module": { + "generics": { + "generics": "< T : BundleValue >", + "where": "T :: Type : BundleType < Value = T > ," + }, + "data": { + "$kind": "Struct", + "$constructor": "Module::new_unchecked", + "name_id()": "Visible" + } + }, + "NameId": { + "data": { + "$kind": "Struct", + "0": "Opaque", + "1": "Opaque" + } + } + } +}"# + ); + } +} diff --git a/crates/fayalite-visit-gen/src/lib.rs b/crates/fayalite-visit-gen/src/lib.rs new file mode 100644 index 0000000..adf1d9b --- /dev/null +++ b/crates/fayalite-visit-gen/src/lib.rs @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use std::{collections::BTreeMap, fs}; +use syn::{fold::Fold, parse_quote}; + +pub mod ast; + +fn map_camel_case_to_snake_case(s: &str) -> String { + #[derive(Clone, Copy, PartialEq, Eq)] + enum State { + Start, + Lowercase, + PushedUpper(char), + } + let mut state = State::Start; + let mut retval = String::new(); + for ch in s.chars() { + state = match ch { + 'A'..='Z' => { + match state { + State::Start => {} + State::Lowercase => retval.push('_'), + State::PushedUpper(upper) => retval.push(upper.to_ascii_lowercase()), + } + State::PushedUpper(ch) + } + _ => { + match state { + State::PushedUpper(upper) => { + retval.push(upper.to_ascii_lowercase()); + } + State::Start | State::Lowercase => {} + } + retval.push(ch); + State::Lowercase + } + }; + } + match state { + State::Lowercase | State::Start => {} + State::PushedUpper(upper) => retval.push(upper.to_ascii_lowercase()), + } + retval +} + +#[derive(Clone)] +struct DefinitionState { + fn_name_suffix: syn::Ident, + generics: syn::Generics, + fold_generics: syn::Generics, + folder_generics: syn::Generics, + visit_generics: syn::Generics, + visitor_generics: syn::Generics, +} + +impl DefinitionState { + fn folder_fn_name(&self) -> syn::Ident { + format_ident!("fold_{}", self.fn_name_suffix) + } + fn visitor_fn_name(&self) -> syn::Ident { + format_ident!("visit_{}", self.fn_name_suffix) + } + fn folder_fn(&self, path: &ast::Path) -> TokenStream { + let folder_fn_name = self.folder_fn_name(); + let (impl_generics, type_generics, where_clause) = self.folder_generics.split_for_impl(); + quote! { + fn #folder_fn_name #impl_generics(&mut self, v: #path #type_generics) -> Result<#path #type_generics, Self::Error> #where_clause { + Fold::default_fold(v, self) + } + } + } + fn visitor_fn(&self, path: &ast::Path) -> TokenStream { + let visitor_fn_name = self.visitor_fn_name(); + let (impl_generics, type_generics, where_clause) = self.visitor_generics.split_for_impl(); + quote! { + fn #visitor_fn_name #impl_generics(&mut self, v: &#path #type_generics) -> Result<(), Self::Error> #where_clause { + Visit::default_visit(v, self) + } + } + } + fn fold_impl(&self, path: &ast::Path, body: impl ToTokens) -> TokenStream { + let folder_fn_name = self.folder_fn_name(); + let (_, self_type_generics, _) = self.generics.split_for_impl(); + let (trait_impl_generics, _, trait_where_clause) = self.fold_generics.split_for_impl(); + quote! { + #[automatically_derived] + #[allow(clippy::init_numbered_fields)] + impl #trait_impl_generics Fold for #path #self_type_generics #trait_where_clause { + fn fold(self, state: &mut State) -> Result { + state.#folder_fn_name(self) + } + fn default_fold(self, state: &mut State) -> Result { + #body + } + } + } + } + fn visit_impl(&self, path: &ast::Path, body: impl ToTokens) -> TokenStream { + let visitor_fn_name = self.visitor_fn_name(); + let (_, self_type_generics, _) = self.generics.split_for_impl(); + let (trait_impl_generics, _, trait_where_clause) = self.visit_generics.split_for_impl(); + quote! { + #[automatically_derived] + impl #trait_impl_generics Visit for #path #self_type_generics #trait_where_clause { + fn visit(&self, state: &mut State) -> Result<(), State::Error> { + state.#visitor_fn_name(self) + } + fn default_visit(&self, state: &mut State) -> Result<(), State::Error> { + #body + } + } + } + } +} + +struct GenerateState<'a> { + def_states: BTreeMap<&'a ast::Path, DefinitionState>, + definitions: &'a ast::Definitions, +} + +struct MapStateToSelf; + +impl syn::fold::Fold for MapStateToSelf { + fn fold_ident(&mut self, i: syn::Ident) -> syn::Ident { + if i == "State" { + syn::Ident::new("Self", i.span()) + } else { + i + } + } +} + +impl<'a> GenerateState<'a> { + fn make_definition_state(&mut self, path: &'a ast::Path) -> syn::Result<()> { + let ast::Definition { + fn_name_suffix, + generics, + fold_where, + visit_where, + data: _, + } = self.definitions.types.get(path).ok_or_else(|| { + syn::Error::new( + Span::call_site(), + format!("can't find named type: {path:?}"), + ) + })?; + let fn_name_suffix = fn_name_suffix + .as_ref() + .map(syn::Ident::from) + .unwrap_or_else(|| format_ident!("{}", map_camel_case_to_snake_case(&path.last().0))); + let generics = generics.clone().map(|v| v.0).unwrap_or_default(); + let mut fold_generics = generics.clone(); + let mut folder_generics = generics.clone(); + fold_generics + .params + .insert(0, parse_quote! {State: ?Sized + Folder}); + if let Some(fold_where) = fold_where { + fold_generics + .make_where_clause() + .predicates + .extend(fold_where.0.iter().cloned()); + folder_generics.make_where_clause().predicates.extend( + fold_where + .0 + .iter() + .cloned() + .map(|v| MapStateToSelf.fold_where_predicate(v)), + ); + } + let mut visit_generics = generics.clone(); + let mut visitor_generics = generics.clone(); + visit_generics + .params + .insert(0, parse_quote! {State: ?Sized + Visitor}); + if let Some(visit_where) = visit_where { + visit_generics + .make_where_clause() + .predicates + .extend(visit_where.0.iter().cloned()); + visitor_generics.make_where_clause().predicates.extend( + visit_where + .0 + .iter() + .cloned() + .map(|v| MapStateToSelf.fold_where_predicate(v)), + ); + } + self.def_states.insert( + path, + DefinitionState { + fn_name_suffix, + generics, + fold_generics, + folder_generics, + visit_generics, + visitor_generics, + }, + ); + Ok(()) + } + fn new(ast: &'a ast::Definitions) -> syn::Result { + let mut retval = GenerateState { + def_states: BTreeMap::new(), + definitions: ast, + }; + let ast::Definitions { types } = ast; + for path in types.keys() { + retval.make_definition_state(path)?; + } + Ok(retval) + } +} + +pub fn generate(ast: &ast::Definitions) -> syn::Result { + let state = GenerateState::new(ast)?; + let mut visitor_fns = vec![]; + let mut visit_impls = vec![]; + let mut folder_fns = vec![]; + let mut fold_impls = vec![]; + for (&def_path, def_state) in state.def_states.iter() { + folder_fns.push(def_state.folder_fn(def_path)); + visitor_fns.push(def_state.visitor_fn(def_path)); + let fold_body; + let visit_body; + let ast::Definition { + fn_name_suffix: _, + generics: _, + fold_where: _, + visit_where: _, + data, + } = ast.types.get(def_path).unwrap(); + match data { + ast::Data::ManualImpl => { + continue; + } + ast::Data::Opaque => { + fold_body = quote! { + let _ = state; + Ok(self) + }; + visit_body = quote! { + let _ = state; + Ok(()) + }; + } + ast::Data::Struct(ast::Fields { + constructor, + fields, + }) => { + let mut visit_members = vec![]; + let mut fold_members = vec![]; + for (field_name, field) in fields { + let fold_member_name = if constructor.is_some() { + None + } else { + let member = field_name.to_member(); + if member.is_none() { + return Err(syn::Error::new(Span::call_site(), format!("struct must have `$constructor` since it contains a non-plain field: {def_path:?} {field_name:?}"))); + } + member + }; + let fold_member_name = fold_member_name.as_slice(); + let fold_member = match field { + ast::Field::Opaque => { + quote! { + #(#fold_member_name:)* self.#field_name + } + } + ast::Field::Visible => { + visit_members.push(quote! { + Visit::visit(&self.#field_name, state)?; + }); + quote! { + #(#fold_member_name:)* Fold::fold(self.#field_name, state)? + } + } + ast::Field::RefVisible => { + visit_members.push(quote! { + Visit::visit(self.#field_name, state)?; + }); + quote! { + #(#fold_member_name:)* Fold::fold(self.#field_name.clone(), state)? + } + } + }; + fold_members.push(fold_member); + } + let match_members = constructor + .is_none() + .then(|| { + fields + .keys() + .map(|k| k.to_member()) + .collect::>>() + .map(|members| { + if members.is_empty() { + quote! { + let _ = state; + let Self {} = self; + } + } else { + quote! { + let Self { + #(#members: _,)* + } = self; + } + } + }) + }) + .flatten(); + visit_body = quote! { + #match_members + #(#visit_members)* + Ok(()) + }; + let fold_body_tail = if let Some(constructor) = constructor { + quote! { + Ok(#constructor(#(#fold_members),*)) + } + } else { + quote! { + Ok(Self { + #(#fold_members,)* + }) + } + }; + fold_body = quote! { + #match_members + #fold_body_tail + }; + } + ast::Data::Enum(ast::Variants { variants }) => { + let mut fold_arms = vec![]; + let mut visit_arms = vec![]; + let mut state_unused = true; + for (variant_name, variant_field) in variants { + let fold_arm; + let visit_arm; + match variant_field { + Some(ast::Field::Visible) => { + state_unused = false; + fold_arm = quote! { + Self::#variant_name(v) => Fold::fold(v, state).map(Self::#variant_name), + }; + visit_arm = quote! { + Self::#variant_name(v) => Visit::visit(v, state), + }; + } + Some(ast::Field::RefVisible) => { + return Err(syn::Error::new( + Span::call_site(), + "enum variant field must not be RefVisible", + )); + } + Some(ast::Field::Opaque) => { + fold_arm = quote! { + Self::#variant_name(_) => Ok(self), + }; + visit_arm = quote! { + Self::#variant_name(_) => Ok(()), + }; + } + None => { + fold_arm = quote! { + Self::#variant_name => Ok(self), + }; + visit_arm = quote! { + Self::#variant_name => Ok(()), + }; + } + } + fold_arms.push(fold_arm); + visit_arms.push(visit_arm); + } + let ignore_state = state_unused.then(|| { + quote! { + let _ = state; + } + }); + visit_body = quote! { + #ignore_state + match self { + #(#visit_arms)* + } + }; + fold_body = quote! { + #ignore_state + match self { + #(#fold_arms)* + } + }; + } + } + fold_impls.push(def_state.fold_impl(def_path, fold_body)); + visit_impls.push(def_state.visit_impl(def_path, visit_body)); + } + Ok(prettyplease::unparse(&parse_quote! { + pub trait Visitor { + type Error; + + #(#visitor_fns)* + } + + #(#visit_impls)* + + pub trait Folder { + type Error; + + #(#folder_fns)* + } + + #(#fold_impls)* + })) +} + +pub fn error_at_call_site(e: T) -> syn::Error { + syn::Error::new(Span::call_site(), e) +} + +pub fn parse_and_generate(path: impl AsRef) -> syn::Result { + let input = fs::read_to_string(path).map_err(error_at_call_site)?; + let ast: ast::Definitions = serde_json::from_str(&input).map_err(error_at_call_site)?; + generate(&ast) +} diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml new file mode 100644 index 0000000..90a0e08 --- /dev/null +++ b/crates/fayalite/Cargo.toml @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# See Notices.txt for copyright information +[package] +name = "fayalite" +version = "0.1.0" +edition = "2021" +workspace = "../.." +license = "LGPL-3.0-or-later" + +[dependencies] +bitvec = { version = "1.0.1", features = ["serde"] } +hashbrown = "0.14.3" +num-bigint = "0.4.4" +num-traits = "0.2.16" +paste = "1.0.14" +fayalite-proc-macros = { version = "=0.1.0", path = "../fayalite-proc-macros" } +serde = { version = "1.0.202", features = ["derive"] } +serde_json = "1.0.117" + +[dev-dependencies] +trybuild = "1.0" + +[build-dependencies] +fayalite-visit-gen = { version = "=0.1.0", path = "../fayalite-visit-gen" } diff --git a/crates/fayalite/build.rs b/crates/fayalite/build.rs new file mode 100644 index 0000000..429e527 --- /dev/null +++ b/crates/fayalite/build.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use fayalite_visit_gen::parse_and_generate; +use std::{env, fs, path::Path}; + +fn main() { + let path = "visit_types.json"; + println!("cargo::rerun-if-changed={path}"); + println!("cargo::rerun-if-changed=build.rs"); + let generated = parse_and_generate(path).map_err(|e| panic!("{e}")).unwrap(); + let out_dir = env::var_os("OUT_DIR").unwrap(); + let out_path = Path::new(&out_dir).join("visit.rs"); + fs::write(&out_path, generated).unwrap(); + // println!("cargo::warning=generated {}", out_path.display()); +} diff --git a/crates/fayalite/src/annotations.rs b/crates/fayalite/src/annotations.rs new file mode 100644 index 0000000..5e2ba95 --- /dev/null +++ b/crates/fayalite/src/annotations.rs @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + expr::Target, + intern::{Intern, Interned}, +}; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + hash::{Hash, Hasher}, + ops::Deref, +}; + +#[derive(Clone)] +struct CustomFirrtlAnnotationFieldsImpl { + value: serde_json::Map, + serialized: Interned, +} + +impl Hash for CustomFirrtlAnnotationFieldsImpl { + fn hash(&self, state: &mut H) { + self.serialized.hash(state); + } +} + +impl Eq for CustomFirrtlAnnotationFieldsImpl {} + +impl PartialEq for CustomFirrtlAnnotationFieldsImpl { + fn eq(&self, other: &Self) -> bool { + self.serialized == other.serialized + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct CustomFirrtlAnnotationFields(Interned); + +impl fmt::Debug for CustomFirrtlAnnotationFields { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.value.fmt(f) + } +} + +impl<'de> Deserialize<'de> for CustomFirrtlAnnotationFields { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + serde_json::Map::::deserialize(deserializer).map(Self::from) + } +} + +impl Serialize for CustomFirrtlAnnotationFields { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.value.serialize(serializer) + } +} + +impl Deref for CustomFirrtlAnnotationFields { + type Target = serde_json::Map; + + fn deref(&self) -> &Self::Target { + &self.0.value + } +} + +impl From> for CustomFirrtlAnnotationFields { + fn from(value: serde_json::Map) -> Self { + let serialized = + serde_json::to_string(&value).expect("serialization of JSON should succeed"); + Self(Intern::intern_sized(CustomFirrtlAnnotationFieldsImpl { + value, + serialized: Intern::intern_owned(serialized), + })) + } +} + +#[derive(Debug, Clone)] +pub struct NotAJsonObject(pub serde_json::Value); + +impl fmt::Display for NotAJsonObject { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("not a JSON object") + } +} + +impl std::error::Error for NotAJsonObject {} + +impl TryFrom for CustomFirrtlAnnotationFields { + type Error = NotAJsonObject; + + fn try_from(value: serde_json::Value) -> Result { + match value { + serde_json::Value::Object(value) => Ok(value.into()), + _ => Err(NotAJsonObject(value)), + } + } +} + +impl From for serde_json::Map { + fn from(value: CustomFirrtlAnnotationFields) -> Self { + Self::clone(&value) + } +} + +impl From for serde_json::Value { + fn from(value: CustomFirrtlAnnotationFields) -> Self { + serde_json::Value::Object(value.into()) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)] +pub struct CustomFirrtlAnnotation { + pub class: Interned, + #[serde(flatten)] + pub additional_fields: CustomFirrtlAnnotationFields, +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[non_exhaustive] +pub enum Annotation { + DontTouch, + CustomFirrtl(CustomFirrtlAnnotation), +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct TargetedAnnotation { + target: Interned, + annotation: Annotation, +} + +impl TargetedAnnotation { + #[track_caller] + pub fn new(target: Interned, annotation: Annotation) -> Self { + Self::assert_valid_target(target); + Self { target, annotation } + } + #[track_caller] + pub fn assert_valid_target(target: Interned) { + assert!(target.is_static(), "can't annotate non-static targets"); + } + pub fn target(&self) -> Interned { + self.target + } + pub fn annotation(&self) -> &Annotation { + &self.annotation + } +} + +pub trait IntoAnnotations { + type IntoAnnotations: IntoIterator; + + fn into_annotations(self) -> Self::IntoAnnotations; +} + +impl IntoAnnotations for Annotation { + type IntoAnnotations = [Annotation; 1]; + + fn into_annotations(self) -> Self::IntoAnnotations { + [self] + } +} + +impl IntoAnnotations for Box { + type IntoAnnotations = [Annotation; 1]; + + fn into_annotations(self) -> Self::IntoAnnotations { + [*self] + } +} + +impl IntoAnnotations for &'_ Annotation { + type IntoAnnotations = [Annotation; 1]; + + fn into_annotations(self) -> Self::IntoAnnotations { + [self.clone()] + } +} + +impl IntoAnnotations for &'_ mut Annotation { + type IntoAnnotations = [Annotation; 1]; + + fn into_annotations(self) -> Self::IntoAnnotations { + [self.clone()] + } +} + +impl> IntoAnnotations for T { + type IntoAnnotations = Self; + + fn into_annotations(self) -> Self::IntoAnnotations { + self + } +} diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs new file mode 100644 index 0000000..c0db3c2 --- /dev/null +++ b/crates/fayalite/src/array.rs @@ -0,0 +1,729 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + bundle::{BundleType, BundleValue}, + expr::{ + ops::{ArrayIndex, ArrayLiteral, ExprIndex}, + Expr, ToExpr, + }, + intern::{Intern, Interned, InternedCompare, Memoize}, + module::{ + transform::visit::{Fold, Folder, Visit, Visitor}, + ModuleBuilder, NormalModule, + }, + source_location::SourceLocation, + ty::{ + CanonicalType, CanonicalTypeKind, CanonicalValue, Connect, DynCanonicalType, + DynCanonicalValue, DynType, DynValueTrait, FixedType, MatchVariantWithoutScope, Type, + TypeEnum, Value, ValueEnum, + }, + util::{ConstBool, GenericConstBool, MakeMutSlice}, +}; +use bitvec::{slice::BitSlice, vec::BitVec}; +use std::{ + any::Any, + borrow::{Borrow, BorrowMut}, + fmt, + hash::Hash, + marker::PhantomData, + ops::IndexMut, + sync::Arc, +}; + +mod sealed { + pub trait Sealed {} +} + +pub trait ValueArrayOrSlice: + sealed::Sealed + + BorrowMut<[::Element]> + + AsRef<[::Element]> + + AsMut<[::Element]> + + Hash + + fmt::Debug + + Eq + + Send + + Sync + + 'static + + IndexMut::Element> + + ToOwned + + InternedCompare +{ + type Element: Value::ElementType>; + type ElementType: Type::Element>; + type LenType: 'static + Copy + Ord + fmt::Debug + Hash + Send + Sync; + type Match: 'static + + Clone + + Eq + + fmt::Debug + + Hash + + Send + + Sync + + BorrowMut<[Expr]>; + type MaskVA: ValueArrayOrSlice< + Element = ::MaskValue, + ElementType = ::MaskType, + LenType = Self::LenType, + MaskVA = Self::MaskVA, + > + ?Sized; + type IsFixedLen: GenericConstBool; + const FIXED_LEN_TYPE: Option; + fn make_match(array: Expr>) -> Self::Match; + fn len_from_len_type(v: Self::LenType) -> usize; + #[allow(clippy::result_unit_err)] + fn try_len_type_from_len(v: usize) -> Result; + fn len_type(&self) -> Self::LenType; + fn len(&self) -> usize; + fn is_empty(&self) -> bool; + fn iter(&self) -> std::slice::Iter { + Borrow::<[_]>::borrow(self).iter() + } + fn clone_to_arc(&self) -> Arc; + fn arc_make_mut(v: &mut Arc) -> &mut Self; + fn arc_to_arc_slice(self: Arc) -> Arc<[Self::Element]>; +} + +impl sealed::Sealed for [T] {} + +impl ValueArrayOrSlice for [V] +where + V::Type: Type, +{ + type Element = V; + type ElementType = V::Type; + type LenType = usize; + type Match = Box<[Expr]>; + type MaskVA = [::MaskValue]; + type IsFixedLen = ConstBool; + const FIXED_LEN_TYPE: Option = None; + + fn make_match(array: Expr>) -> Self::Match { + (0..array.canonical_type().len()) + .map(|index| ArrayIndex::::new_unchecked(array.canonical(), index).to_expr()) + .collect() + } + + fn len_from_len_type(v: Self::LenType) -> usize { + v + } + + fn try_len_type_from_len(v: usize) -> Result { + Ok(v) + } + + fn len_type(&self) -> Self::LenType { + self.len() + } + + fn len(&self) -> usize { + <[_]>::len(self) + } + + fn is_empty(&self) -> bool { + <[_]>::is_empty(self) + } + + fn clone_to_arc(&self) -> Arc { + Arc::from(self) + } + + fn arc_make_mut(v: &mut Arc) -> &mut Self { + MakeMutSlice::make_mut_slice(v) + } + + fn arc_to_arc_slice(self: Arc) -> Arc<[Self::Element]> { + self + } +} + +impl sealed::Sealed for [T; N] {} + +impl ValueArrayOrSlice for [V; N] +where + V::Type: Type, +{ + type Element = V; + type ElementType = V::Type; + type LenType = (); + type Match = [Expr; N]; + type MaskVA = [::MaskValue; N]; + type IsFixedLen = ConstBool; + const FIXED_LEN_TYPE: Option = Some(()); + + fn make_match(array: Expr>) -> Self::Match { + std::array::from_fn(|index| { + ArrayIndex::::new_unchecked(array.canonical(), index).to_expr() + }) + } + + fn len_from_len_type(_v: Self::LenType) -> usize { + N + } + + fn try_len_type_from_len(v: usize) -> Result { + if v == N { + Ok(()) + } else { + Err(()) + } + } + + fn len_type(&self) -> Self::LenType {} + + fn len(&self) -> usize { + N + } + + fn is_empty(&self) -> bool { + N == 0 + } + + fn clone_to_arc(&self) -> Arc { + Arc::new(self.clone()) + } + + fn arc_make_mut(v: &mut Arc) -> &mut Self { + Arc::make_mut(v) + } + + fn arc_to_arc_slice(self: Arc) -> Arc<[Self::Element]> { + self + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct ArrayType { + element: VA::ElementType, + len: VA::LenType, + bit_width: usize, +} + +pub trait ArrayTypeTrait: + Type< + CanonicalType = ArrayType<[DynCanonicalValue]>, + Value = Array<::ValueArrayOrSlice>, + CanonicalValue = Array<[DynCanonicalValue]>, + MaskType = ArrayType< + <::ValueArrayOrSlice as ValueArrayOrSlice>::MaskVA, + >, + > + From::ValueArrayOrSlice>> + + Into::ValueArrayOrSlice>> + + BorrowMut::ValueArrayOrSlice>> + + sealed::Sealed + + Connect +{ + type ValueArrayOrSlice: ValueArrayOrSlice + + ?Sized; + type Element: Value; + type ElementType: Type; +} + +impl sealed::Sealed for ArrayType {} + +impl ArrayTypeTrait for ArrayType { + type ValueArrayOrSlice = VA; + type Element = VA::Element; + type ElementType = VA::ElementType; +} + +impl Clone for ArrayType { + fn clone(&self) -> Self { + Self { + element: self.element.clone(), + len: self.len, + bit_width: self.bit_width, + } + } +} + +impl Copy for ArrayType where VA::ElementType: Copy {} + +impl ArrayType { + pub fn element(&self) -> &VA::ElementType { + &self.element + } + pub fn len(&self) -> usize { + VA::len_from_len_type(self.len) + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn bit_width(&self) -> usize { + self.bit_width + } + pub fn into_slice_type(self) -> ArrayType<[VA::Element]> { + ArrayType { + len: self.len(), + element: self.element, + bit_width: self.bit_width, + } + } + #[track_caller] + pub fn new_with_len(element: VA::ElementType, len: usize) -> Self { + Self::new_with_len_type( + element, + VA::try_len_type_from_len(len).expect("length should match"), + ) + } + #[track_caller] + pub fn new_with_len_type(element: VA::ElementType, len: VA::LenType) -> Self { + let Some(bit_width) = VA::len_from_len_type(len).checked_mul(element.bit_width()) else { + panic!("array is too big: bit-width overflowed"); + }; + ArrayType { + element, + len, + bit_width, + } + } +} + +impl Fold for ArrayType +where + VA::ElementType: Fold, +{ + fn fold(self, state: &mut State) -> Result { + state.fold_array_type(self) + } + fn default_fold(self, state: &mut State) -> Result { + Ok(Self::new_with_len_type(self.element.fold(state)?, self.len)) + } +} + +impl Visit for ArrayType +where + VA::ElementType: Visit, +{ + fn visit(&self, state: &mut State) -> Result<(), State::Error> { + state.visit_array_type(self) + } + fn default_visit(&self, state: &mut State) -> Result<(), State::Error> { + self.element.visit(state) + } +} + +impl ArrayType<[V; N]> +where + V::Type: Type, +{ + pub fn new_array(element: V::Type) -> Self { + ArrayType::new_with_len_type(element, ()) + } +} + +impl FixedType for ArrayType<[V; N]> +where + V::Type: FixedType, +{ + fn fixed_type() -> Self { + Self::new_array(FixedType::fixed_type()) + } +} + +impl ArrayType<[V]> +where + V::Type: Type, +{ + pub fn new_slice(element: V::Type, len: usize) -> Self { + ArrayType::new_with_len_type(element, len) + } +} + +impl Type for ArrayType { + type CanonicalType = ArrayType<[DynCanonicalValue]>; + type Value = Array; + type CanonicalValue = Array<[DynCanonicalValue]>; + type MaskType = ArrayType; + type MaskValue = Array; + type MatchVariant = VA::Match; + type MatchActiveScope = (); + type MatchVariantAndInactiveScope = MatchVariantWithoutScope; + type MatchVariantsIter = std::iter::Once; + + fn match_variants( + this: Expr, + module_builder: &mut ModuleBuilder, + source_location: SourceLocation, + ) -> Self::MatchVariantsIter + where + IO::Type: BundleType, + { + let _ = module_builder; + let _ = source_location; + std::iter::once(MatchVariantWithoutScope(VA::make_match(this))) + } + + fn mask_type(&self) -> Self::MaskType { + #[derive(Clone, Hash, Eq, PartialEq)] + struct ArrayMaskTypeMemoize(PhantomData); + impl Copy for ArrayMaskTypeMemoize {} + impl Memoize for ArrayMaskTypeMemoize { + type Input = ArrayType; + type InputOwned = ArrayType; + type Output = as Type>::MaskType; + + fn inner(self, input: &Self::Input) -> Self::Output { + ArrayType::new_with_len_type(input.element.mask_type(), input.len) + } + } + ArrayMaskTypeMemoize::(PhantomData).get(self) + } + + fn canonical(&self) -> Self::CanonicalType { + ArrayType { + element: self.element.canonical_dyn(), + len: self.len(), + bit_width: self.bit_width, + } + } + + fn source_location(&self) -> SourceLocation { + SourceLocation::builtin() + } + + fn type_enum(&self) -> TypeEnum { + TypeEnum::ArrayType(self.canonical()) + } + + fn from_canonical_type(t: Self::CanonicalType) -> Self { + Self { + element: VA::ElementType::from_dyn_canonical_type(t.element), + len: VA::try_len_type_from_len(t.len).expect("length should match"), + bit_width: t.bit_width, + } + } + + fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> { + Some(::downcast_ref::>( + this, + )?) + } +} + +impl Connect> + for ArrayType +{ +} + +impl CanonicalType for ArrayType<[DynCanonicalValue]> { + const CANONICAL_TYPE_KIND: CanonicalTypeKind = CanonicalTypeKind::ArrayType; +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct Array { + element_ty: VA::ElementType, + value: Arc, +} + +impl Clone for Array { + fn clone(&self) -> Self { + Self { + element_ty: self.element_ty.clone(), + value: self.value.clone(), + } + } +} + +impl ToExpr for Array { + type Type = ArrayType; + + fn ty(&self) -> Self::Type { + ArrayType::new_with_len_type(self.element_ty.clone(), self.value.len_type()) + } + + fn to_expr(&self) -> Expr<::Value> { + Expr::from_value(self) + } +} + +impl Value for Array { + fn to_canonical(&self) -> ::CanonicalValue { + Array { + element_ty: self.element_ty.canonical_dyn(), + value: AsRef::<[_]>::as_ref(&*self.value) + .iter() + .map(|v| v.to_canonical_dyn()) + .collect(), + } + } + fn to_bits_impl(this: &Self) -> Interned { + #[derive(Hash, Eq, PartialEq)] + struct ArrayToBitsMemoize(PhantomData); + impl Clone for ArrayToBitsMemoize { + fn clone(&self) -> Self { + *self + } + } + impl Copy for ArrayToBitsMemoize {} + impl Memoize for ArrayToBitsMemoize { + type Input = Array; + type InputOwned = Array; + type Output = Interned; + + fn inner(self, input: &Self::Input) -> Self::Output { + let mut bits = BitVec::with_capacity(input.ty().bit_width()); + for element in AsRef::<[_]>::as_ref(&*input.value).iter() { + bits.extend_from_bitslice(&element.to_bits()); + } + Intern::intern_owned(bits) + } + } + ArrayToBitsMemoize::(PhantomData).get(this) + } +} + +impl CanonicalValue for Array<[DynCanonicalValue]> { + fn value_enum_impl(this: &Self) -> ValueEnum { + ValueEnum::Array(this.clone()) + } + fn to_bits_impl(this: &Self) -> Interned { + Value::to_bits_impl(this) + } +} + +impl Array { + pub fn element_ty(&self) -> &VA::ElementType { + &self.element_ty + } + pub fn len(&self) -> usize { + VA::len_from_len_type(self.value.len_type()) + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn value(&self) -> &Arc { + &self.value + } + pub fn set_element(&mut self, index: usize, element: VA::Element) { + assert_eq!(self.element_ty, element.ty()); + VA::arc_make_mut(&mut self.value)[index] = element; + } + pub fn new(element_ty: VA::ElementType, value: Arc) -> Self { + for element in value.iter() { + assert_eq!(element_ty, element.ty()); + } + Self { element_ty, value } + } + pub fn into_slice(self) -> Array<[VA::Element]> { + Array { + element_ty: self.element_ty, + value: self.value.arc_to_arc_slice(), + } + } +} + +impl>> From for Array +where + VA::ElementType: FixedType, +{ + fn from(value: T) -> Self { + Self::new(FixedType::fixed_type(), value.into()) + } +} + +impl, T: FixedType> ToExpr for [E] { + type Type = ArrayType<[T::Value]>; + + fn ty(&self) -> Self::Type { + ArrayType::new_with_len_type(FixedType::fixed_type(), self.len()) + } + + fn to_expr(&self) -> Expr<::Value> { + let elements = Intern::intern_owned(Vec::from_iter( + self.iter().map(|v| v.to_expr().to_canonical_dyn()), + )); + ArrayLiteral::new_unchecked(elements, self.ty()).to_expr() + } +} + +impl, T: FixedType> ToExpr for Vec { + type Type = ArrayType<[T::Value]>; + + fn ty(&self) -> Self::Type { + <[E]>::ty(self) + } + + fn to_expr(&self) -> Expr<::Value> { + <[E]>::to_expr(self) + } +} + +impl, T: FixedType> ToExpr for [E; 0] { + type Type = ArrayType<[T::Value; 0]>; + + fn ty(&self) -> Self::Type { + ArrayType::new_with_len_type(FixedType::fixed_type(), ()) + } + + fn to_expr(&self) -> Expr<::Value> { + Array::new(FixedType::fixed_type(), Arc::new([])).to_expr() + } +} + +macro_rules! impl_to_expr_for_non_empty_array { + ($N:literal) => { + impl, T: Type> ToExpr for [E; $N] { + type Type = ArrayType<[T::Value; $N]>; + + fn ty(&self) -> Self::Type { + ArrayType::new_with_len_type(self[0].ty(), ()) + } + + fn to_expr(&self) -> Expr<::Value> { + let elements = Intern::intern_owned(Vec::from_iter( + self.iter().map(|v| v.to_expr().to_canonical_dyn()), + )); + ArrayLiteral::new_unchecked(elements, self.ty()).to_expr() + } + } + }; +} + +impl_to_expr_for_non_empty_array!(1); +impl_to_expr_for_non_empty_array!(2); +impl_to_expr_for_non_empty_array!(3); +impl_to_expr_for_non_empty_array!(4); +impl_to_expr_for_non_empty_array!(5); +impl_to_expr_for_non_empty_array!(6); +impl_to_expr_for_non_empty_array!(7); +impl_to_expr_for_non_empty_array!(8); +impl_to_expr_for_non_empty_array!(9); +impl_to_expr_for_non_empty_array!(10); +impl_to_expr_for_non_empty_array!(11); +impl_to_expr_for_non_empty_array!(12); +impl_to_expr_for_non_empty_array!(13); +impl_to_expr_for_non_empty_array!(14); +impl_to_expr_for_non_empty_array!(15); +impl_to_expr_for_non_empty_array!(16); +impl_to_expr_for_non_empty_array!(17); +impl_to_expr_for_non_empty_array!(18); +impl_to_expr_for_non_empty_array!(19); +impl_to_expr_for_non_empty_array!(20); +impl_to_expr_for_non_empty_array!(21); +impl_to_expr_for_non_empty_array!(22); +impl_to_expr_for_non_empty_array!(23); +impl_to_expr_for_non_empty_array!(24); +impl_to_expr_for_non_empty_array!(25); +impl_to_expr_for_non_empty_array!(26); +impl_to_expr_for_non_empty_array!(27); +impl_to_expr_for_non_empty_array!(28); +impl_to_expr_for_non_empty_array!(29); +impl_to_expr_for_non_empty_array!(30); +impl_to_expr_for_non_empty_array!(31); +impl_to_expr_for_non_empty_array!(32); + +#[derive(Clone, Debug)] +pub struct ArrayIntoIter { + array: Arc, + indexes: std::ops::Range, +} + +impl Iterator for ArrayIntoIter { + type Item = VA::Element; + + fn next(&mut self) -> Option { + Some(self.array[self.indexes.next()?].clone()) + } + + fn size_hint(&self) -> (usize, Option) { + self.indexes.size_hint() + } +} + +impl std::iter::FusedIterator for ArrayIntoIter {} + +impl ExactSizeIterator for ArrayIntoIter {} + +impl DoubleEndedIterator for ArrayIntoIter { + fn next_back(&mut self) -> Option { + Some(self.array[self.indexes.next_back()?].clone()) + } +} + +impl Array { + pub fn iter(&self) -> std::slice::Iter<'_, VA::Element> { + self.value.iter() + } +} + +impl<'a, VA: ValueArrayOrSlice> IntoIterator for &'a Array { + type Item = &'a VA::Element; + type IntoIter = std::slice::Iter<'a, VA::Element>; + + fn into_iter(self) -> Self::IntoIter { + self.value.iter() + } +} + +impl IntoIterator for Array { + type Item = VA::Element; + type IntoIter = ArrayIntoIter; + + fn into_iter(self) -> Self::IntoIter { + ArrayIntoIter { + indexes: 0..self.len(), + array: self.value, + } + } +} + +#[derive(Clone, Debug)] +pub struct ArrayExprIter { + array: Expr>, + indexes: std::ops::Range, +} + +impl Iterator for ArrayExprIter { + type Item = Expr; + + fn next(&mut self) -> Option { + Some(ExprIndex::expr_index(self.array, self.indexes.next()?)) + } + + fn size_hint(&self) -> (usize, Option) { + self.indexes.size_hint() + } +} + +impl std::iter::FusedIterator for ArrayExprIter {} + +impl ExactSizeIterator for ArrayExprIter {} + +impl DoubleEndedIterator for ArrayExprIter { + fn next_back(&mut self) -> Option { + Some(ExprIndex::expr_index(self.array, self.indexes.next_back()?)) + } +} + +impl IntoIterator for Expr> { + type Item = Expr; + type IntoIter = ArrayExprIter; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl IntoIterator for &'_ Expr> { + type Item = Expr; + type IntoIter = ArrayExprIter; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl Expr> { + pub fn len(self) -> usize { + self.canonical_type().len() + } + pub fn is_empty(self) -> bool { + self.canonical_type().is_empty() + } + pub fn iter(self) -> ArrayExprIter { + ArrayExprIter { + indexes: 0..self.len(), + array: self, + } + } +} diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs new file mode 100644 index 0000000..e2b217b --- /dev/null +++ b/crates/fayalite/src/bundle.rs @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + expr::{ops::BundleLiteral, Expr, ToExpr}, + intern::{ + Intern, Interned, InternedCompare, Memoize, PtrEqWithTypeId, SupportsPtrEqWithTypeId, + }, + module::{ModuleBuilder, NormalModule}, + source_location::SourceLocation, + ty::{ + CanonicalType, CanonicalTypeKind, CanonicalValue, Connect, DynCanonicalType, + DynCanonicalValue, DynType, FixedType, MatchVariantWithoutScope, Type, TypeEnum, + TypeWithDeref, Value, ValueEnum, + }, +}; +use bitvec::{slice::BitSlice, vec::BitVec}; +use hashbrown::HashMap; +use std::{ + fmt, + hash::{Hash, Hasher}, + marker::PhantomData, + sync::Arc, +}; + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct FieldType { + pub name: Interned, + pub flipped: bool, + pub ty: T, +} + +pub struct FmtDebugInStruct<'a, T> { + field: &'a FieldType, + field_offset: usize, +} + +impl fmt::Debug for FmtDebugInStruct<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + field: + &FieldType { + name, + flipped, + ref ty, + }, + field_offset, + } = *self; + if flipped { + write!(f, "#[hdl(flip)] ")?; + } + if f.alternate() { + writeln!(f, "/* offset = {field_offset} */")?; + } + write!(f, "{name}: ")?; + ty.fmt(f) + } +} + +impl fmt::Display for FmtDebugInStruct<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl FieldType { + pub fn map_ty U>(self, f: F) -> FieldType { + let Self { name, flipped, ty } = self; + FieldType { + name, + flipped, + ty: f(ty), + } + } + pub fn as_ref_ty(&self) -> FieldType<&T> { + FieldType { + name: self.name, + flipped: self.flipped, + ty: &self.ty, + } + } + pub fn fmt_debug_in_struct(&self, field_offset: usize) -> FmtDebugInStruct<'_, T> { + FmtDebugInStruct { + field: self, + field_offset, + } + } +} + +impl FieldType { + pub fn canonical(&self) -> FieldType { + FieldType { + name: self.name, + flipped: self.flipped, + ty: self.ty.canonical(), + } + } + pub fn to_dyn(&self) -> FieldType> { + FieldType { + name: self.name, + flipped: self.flipped, + ty: self.ty.to_dyn(), + } + } + pub fn canonical_dyn(&self) -> FieldType> { + FieldType { + name: self.name, + flipped: self.flipped, + ty: self.ty.canonical_dyn(), + } + } +} + +impl FieldType> { + pub fn from_canonical_type_helper( + self, + expected_name: &str, + expected_flipped: bool, + ) -> T { + assert_eq!(&*self.name, expected_name, "field name doesn't match"); + assert_eq!( + self.flipped, expected_flipped, + "field {expected_name} orientation (flipped or not) doesn't match" + ); + let ty = &*self.ty; + if let Ok(ty) = ::downcast(ty) { + return T::from_canonical_type(ty); + } + let type_name = std::any::type_name::(); + panic!("field {expected_name} type doesn't match, expected: {type_name:?}, got: {ty:?}"); + } +} + +#[derive(Clone, Eq)] +struct DynBundleTypeImpl { + fields: Interned<[FieldType>]>, + name_indexes: HashMap, usize>, + field_offsets: Interned<[usize]>, + is_passive: bool, + is_storable: bool, + is_castable_from_bits: bool, + bit_width: usize, +} + +impl fmt::Debug for DynBundleTypeImpl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "DynBundleType ")?; + f.debug_set() + .entries( + self.fields + .iter() + .enumerate() + .map(|(index, field)| field.fmt_debug_in_struct(self.field_offsets[index])), + ) + .finish() + } +} + +impl PartialEq for DynBundleTypeImpl { + fn eq(&self, other: &Self) -> bool { + self.fields == other.fields + } +} + +impl Hash for DynBundleTypeImpl { + fn hash(&self, state: &mut H) { + self.fields.hash(state); + } +} + +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct DynBundleType(Interned); + +impl fmt::Debug for DynBundleType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl DynBundleType { + pub fn new(fields: Interned<[FieldType>]>) -> Self { + let is_passive = fields + .iter() + .all(|field| !field.flipped && field.ty.is_passive()); + let is_storable = fields + .iter() + .all(|field| !field.flipped && field.ty.is_storable()); + let is_castable_from_bits = fields + .iter() + .all(|field| !field.flipped && field.ty.is_castable_from_bits()); + let mut name_indexes = HashMap::with_capacity(fields.len()); + let mut field_offsets = Vec::with_capacity(fields.len()); + let mut bit_width = 0usize; + for (index, &FieldType { name, ty, .. }) in fields.iter().enumerate() { + if let Some(old_index) = name_indexes.insert(name, index) { + panic!("duplicate field name {name:?}: at both index {old_index} and {index}"); + } + field_offsets.push(bit_width); + bit_width = bit_width + .checked_add(ty.bit_width()) + .unwrap_or_else(|| panic!("bundle is too big: bit-width overflowed")); + } + Self( + DynBundleTypeImpl { + fields, + name_indexes, + field_offsets: Intern::intern_owned(field_offsets), + is_passive, + is_storable, + is_castable_from_bits, + bit_width, + } + .intern_sized(), + ) + } + pub fn is_passive(self) -> bool { + self.0.is_passive + } + pub fn is_storable(self) -> bool { + self.0.is_storable + } + pub fn is_castable_from_bits(self) -> bool { + self.0.is_castable_from_bits + } + pub fn bit_width(self) -> usize { + self.0.bit_width + } + pub fn name_indexes(&self) -> &HashMap, usize> { + &self.0.name_indexes + } + pub fn field_by_name( + &self, + name: Interned, + ) -> Option>> { + Some(self.0.fields[*self.0.name_indexes.get(&name)?]) + } + pub fn field_offsets(self) -> Interned<[usize]> { + self.0.field_offsets + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct DynBundle { + ty: DynBundleType, + fields: Arc<[DynCanonicalValue]>, +} + +impl DynBundle { + pub fn new(ty: DynBundleType, fields: Arc<[DynCanonicalValue]>) -> Self { + assert_eq!( + ty.fields().len(), + fields.len(), + "field values don't match type" + ); + for (field_ty, field) in ty.fields().iter().zip(fields.iter()) { + assert_eq!(field_ty.ty, field.ty(), "field value doesn't match type"); + } + DynBundle { ty, fields } + } + pub fn fields(&self) -> &Arc<[DynCanonicalValue]> { + &self.fields + } +} + +pub trait TypeHintTrait: Send + Sync + fmt::Debug + SupportsPtrEqWithTypeId { + fn matches(&self, ty: &dyn DynType) -> Result<(), String>; +} + +impl InternedCompare for dyn TypeHintTrait { + type InternedCompareKey = PtrEqWithTypeId; + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + Self::get_ptr_eq_with_type_id(this) + } + fn interned_compare_key_weak(this: &std::sync::Weak) -> Self::InternedCompareKey { + Self::get_ptr_eq_with_type_id(&*this.upgrade().unwrap()) + } +} + +pub struct TypeHint(PhantomData); + +impl TypeHint { + pub fn intern_dyn() -> Interned { + Interned::cast_unchecked( + Self(PhantomData).intern_sized(), + |v| -> &dyn TypeHintTrait { v }, + ) + } +} + +impl fmt::Debug for TypeHint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TypeHint<{}>", std::any::type_name::()) + } +} + +impl Hash for TypeHint { + fn hash(&self, _state: &mut H) {} +} + +impl Eq for TypeHint {} + +impl PartialEq for TypeHint { + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl Clone for TypeHint { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for TypeHint {} + +impl TypeHintTrait for TypeHint { + fn matches(&self, ty: &dyn DynType) -> Result<(), String> { + match ty.downcast::() { + Ok(_) => Ok(()), + Err(_) => Err(format!("can't cast {ty:?} to {self:?}")), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct FieldsHint { + pub known_fields: Interned<[FieldType>]>, + pub more_fields: bool, +} + +impl FieldsHint { + pub fn new( + known_fields: impl IntoIterator>>, + more_fields: bool, + ) -> Self { + let known_fields = Intern::intern_owned(Vec::from_iter(known_fields)); + Self { + known_fields, + more_fields, + } + } + pub fn check_field(self, index: usize, field: FieldType<&dyn DynType>) -> Result<(), String> { + let Some(&known_field) = self.known_fields.get(index) else { + return if self.more_fields { + Ok(()) + } else { + Err(format!( + "too many fields: name={:?} index={index}", + field.name + )) + }; + }; + let FieldType { + name: known_name, + flipped: known_flipped, + ty: type_hint, + } = known_field; + let FieldType { name, flipped, ty } = field; + if name != known_name { + Err(format!( + "wrong field name {name:?}, expected {known_name:?}" + )) + } else if flipped != known_flipped { + Err(format!( + "wrong field direction: flipped={flipped:?}, expected flipped={known_flipped}" + )) + } else { + type_hint.matches(ty) + } + } +} + +pub trait BundleType: + Type + TypeWithDeref + Connect +where + Self::Value: BundleValue + ToExpr, +{ + type Builder; + fn builder() -> Self::Builder; + fn fields(&self) -> Interned<[FieldType>]>; + fn fields_hint() -> FieldsHint; +} + +pub trait BundleValue: Value +where + ::Type: BundleType, +{ + fn to_bits_impl(this: &Self) -> Interned { + #[derive(Hash, Eq, PartialEq)] + struct ToBitsMemoize(PhantomData); + impl Clone for ToBitsMemoize { + fn clone(&self) -> Self { + *self + } + } + impl Copy for ToBitsMemoize {} + impl>> Memoize for ToBitsMemoize { + type Input = T; + type InputOwned = T; + type Output = Interned; + + fn inner(self, input: &Self::Input) -> Self::Output { + let input = input.to_canonical(); + let mut bits = BitVec::with_capacity(input.ty.bit_width()); + for field in input.fields.iter() { + bits.extend_from_bitslice(&field.to_bits()); + } + Intern::intern_owned(bits) + } + } + ToBitsMemoize::(PhantomData).get(this) + } +} + +pub struct DynBundleMatch; + +impl Type for DynBundleType { + type CanonicalType = DynBundleType; + type Value = DynBundle; + type CanonicalValue = DynBundle; + type MaskType = DynBundleType; + type MaskValue = DynBundle; + type MatchVariant = DynBundleMatch; + type MatchActiveScope = (); + type MatchVariantAndInactiveScope = MatchVariantWithoutScope; + type MatchVariantsIter = std::iter::Once; + + fn match_variants( + this: Expr, + module_builder: &mut ModuleBuilder, + source_location: SourceLocation, + ) -> Self::MatchVariantsIter + where + IO::Type: BundleType, + { + let _ = this; + let _ = module_builder; + let _ = source_location; + std::iter::once(MatchVariantWithoutScope(DynBundleMatch)) + } + + fn mask_type(&self) -> Self::MaskType { + #[derive(Copy, Clone, Eq, PartialEq, Hash)] + struct Impl; + + impl Memoize for Impl { + type Input = DynBundleType; + type InputOwned = DynBundleType; + type Output = DynBundleType; + + fn inner(self, input: &Self::Input) -> Self::Output { + DynBundleType::new(Intern::intern_owned(Vec::from_iter( + input + .fields() + .iter() + .map(|&FieldType { name, flipped, ty }| FieldType { + name, + flipped, + ty: ty.mask_type().canonical(), + }), + ))) + } + } + Impl.get(self) + } + + fn canonical(&self) -> Self::CanonicalType { + *self + } + + fn source_location(&self) -> SourceLocation { + SourceLocation::builtin() + } + + fn type_enum(&self) -> TypeEnum { + TypeEnum::BundleType(*self) + } + + fn from_canonical_type(t: Self::CanonicalType) -> Self { + t + } + + fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> { + Some(this) + } +} + +pub struct NoBuilder; + +impl TypeWithDeref for DynBundleType { + fn expr_deref(this: &Expr) -> &Self::MatchVariant { + let _ = this; + &DynBundleMatch + } +} + +impl Connect for DynBundleType {} + +impl BundleType for DynBundleType { + type Builder = NoBuilder; + + fn builder() -> Self::Builder { + NoBuilder + } + + fn fields(&self) -> Interned<[FieldType>]> { + self.0.fields + } + + fn fields_hint() -> FieldsHint { + FieldsHint { + known_fields: [][..].intern(), + more_fields: true, + } + } +} + +impl CanonicalType for DynBundleType { + const CANONICAL_TYPE_KIND: CanonicalTypeKind = CanonicalTypeKind::BundleType; +} + +impl ToExpr for DynBundle { + type Type = DynBundleType; + + fn ty(&self) -> Self::Type { + self.ty + } + + fn to_expr(&self) -> Expr<::Value> { + Expr::from_value(self) + } +} + +impl Value for DynBundle { + fn to_canonical(&self) -> ::CanonicalValue { + self.clone() + } + fn to_bits_impl(this: &Self) -> Interned { + BundleValue::to_bits_impl(this) + } +} + +impl BundleValue for DynBundle {} + +impl CanonicalValue for DynBundle { + fn value_enum_impl(this: &Self) -> ValueEnum { + ValueEnum::Bundle(this.clone()) + } + fn to_bits_impl(this: &Self) -> Interned { + BundleValue::to_bits_impl(this) + } +} + +macro_rules! impl_tuple_builder { + ($builder:ident, [ + $(($before_Ts:ident $before_fields:ident $before_members:literal))* + ] [ + ($T:ident $field:ident $m:literal) + $(($after_Ts:ident $after_fields:ident $after_members:literal))* + ]) => { + impl_tuple_builder!($builder, [ + $(($before_Ts $before_fields $before_members))* + ($T $field $m) + ] [ + $(($after_Ts $after_fields $after_members))* + ]); + + impl $builder { + pub fn $field<$T: ToExpr>(self, $field: $T) -> $builder::Value> $(, $after_Ts)*> { + let Self { + $($before_fields,)* + $field: _, + $($after_fields, )* + _phantom: _, + } = self; + let $field = $field.to_expr(); + $builder { + $($before_fields,)* + $field, + $($after_fields,)* + _phantom: PhantomData, + } + } + } + }; + ($builder:ident, [$($before:tt)*] []) => {}; +} + +macro_rules! into_unit { + ($($tt:tt)*) => { + () + }; +} + +macro_rules! impl_tuple { + ($builder:ident, $(($T:ident $T2:ident $field:ident $m:tt)),*) => { + pub struct $builder { + $($field: $T,)* + _phantom: PhantomData, + } + + impl_tuple_builder!($builder, [] [$(($T $field $m))*]); + + impl<$($T: Value),*> $builder<($($T,)*), $(Expr<$T>,)*> + where + $($T::Type: Type,)* + { + pub fn build(self) -> Expr<($($T,)*)> { + let Self { + $($field,)* + _phantom: _, + } = self; + BundleLiteral::new_unchecked( + [$($field.to_canonical_dyn()),*][..].intern(), + ($($field.ty(),)*), + ).to_expr() + } + } + + impl<$($T: ToExpr,)*> ToExpr for ($($T,)*) { + type Type = ($($T::Type,)*); + + #[allow(clippy::unused_unit)] + fn ty(&self) -> Self::Type { + let ($($field,)*) = self; + ($($field.ty(),)*) + } + + fn to_expr(&self) -> Expr<::Value> { + let ($($field,)*) = self; + $(let $field = $field.to_expr();)* + BundleLiteral::new_unchecked( + [$($field.to_canonical_dyn()),*][..].intern(), + ($($field.ty(),)*), + ).to_expr() + } + } + + impl<$($T, $T2,)*> Connect<($($T2,)*)> for ($($T,)*) + where + $($T: Connect<$T2>,)* + { + } + + impl<$($T: Type,)*> Type for ($($T,)*) + where + $($T::Value: Value,)* + { + type CanonicalType = DynBundleType; + type Value = ($($T::Value,)*); + type CanonicalValue = DynBundle; + type MaskType = ($($T::MaskType,)*); + type MaskValue = ($($T::MaskValue,)*); + type MatchVariant = ($(Expr<$T::Value>,)*); + type MatchActiveScope = (); + type MatchVariantAndInactiveScope = MatchVariantWithoutScope; + type MatchVariantsIter = std::iter::Once; + + fn match_variants( + this: Expr, + module_builder: &mut ModuleBuilder, + source_location: SourceLocation, + ) -> Self::MatchVariantsIter + where + IO::Type: BundleType, + { + let _ = this; + let _ = module_builder; + let _ = source_location; + std::iter::once(MatchVariantWithoutScope(($(this.field(stringify!($m)),)*))) + } + + #[allow(clippy::unused_unit)] + fn mask_type(&self) -> Self::MaskType { + let ($($field,)*) = self; + ($($field.mask_type(),)*) + } + + fn canonical(&self) -> Self::CanonicalType { + DynBundleType::new(self.fields()) + } + + fn source_location(&self) -> SourceLocation { + SourceLocation::builtin() + } + + fn type_enum(&self) -> TypeEnum { + TypeEnum::BundleType(self.canonical()) + } + + #[allow(clippy::unused_unit)] + fn from_canonical_type(t: Self::CanonicalType) -> Self { + let [$($field),*] = *t.fields() else { + panic!("wrong number of fields"); + }; + ($($field.from_canonical_type_helper(stringify!($m), false),)*) + } + } + + impl<$($T: Type,)*> TypeWithDeref for ($($T,)*) + where + $($T::Value: Value,)* + { + fn expr_deref( + this: &::fayalite::expr::Expr<::Value>, + ) -> &::MatchVariant { + let _ = this; + Interned::<_>::into_inner( + Intern::intern_sized(( + $(this.field(stringify!($m)),)* + )), + ) + } + } + + impl<$($T: Type,)*> BundleType for ($($T,)*) + where + $($T::Value: Value,)* + { + type Builder = $builder<($($T::Value,)*), $(into_unit!($T),)*>; + fn builder() -> Self::Builder { + $builder { + $($field: (),)* + _phantom: PhantomData, + } + } + fn fields( + &self, + ) -> Interned<[FieldType>]> { + [ + $(FieldType { + name: stringify!($m).intern(), + flipped: false, + ty: self.$m.canonical_dyn(), + },)* + ][..].intern() + } + fn fields_hint() -> FieldsHint { + FieldsHint::new([ + $(FieldType { + name: stringify!($m).intern(), + flipped: false, + ty: TypeHint::<$T>::intern_dyn(), + },)* + ], false) + } + } + + impl<$($T: FixedType,)*> FixedType for ($($T,)*) + where + $($T::Value: Value,)* + { + #[allow(clippy::unused_unit)] + fn fixed_type() -> Self { + ($($T::fixed_type(),)*) + } + } + + impl<$($T: Value,)*> Value for ($($T,)*) + where + $($T::Type: Type,)* + { + fn to_canonical(&self) -> ::CanonicalValue { + let ty = self.ty().canonical(); + DynBundle::new( + ty, + Arc::new([ + $(self.$m.to_canonical_dyn(),)* + ]), + ) + } + fn to_bits_impl(this: &Self) -> Interned { + BundleValue::to_bits_impl(this) + } + } + + impl<$($T: Value,)*> BundleValue for ($($T,)*) + where + $($T::Type: Type,)* + { + } + }; +} + +impl_tuple!(TupleBuilder0,); +impl_tuple!(TupleBuilder1, (A A2 field_0 0)); +impl_tuple!(TupleBuilder2, (A A2 field_0 0), (B B2 field_1 1)); +impl_tuple!(TupleBuilder3, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2)); +impl_tuple!(TupleBuilder4, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2), (D D2 field_3 3)); +impl_tuple!(TupleBuilder5, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2), (D D2 field_3 3), (E E2 field_4 4)); +impl_tuple!(TupleBuilder6, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2), (D D2 field_3 3), (E E2 field_4 4), (F F2 field_5 5)); +impl_tuple!(TupleBuilder7, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2), (D D2 field_3 3), (E E2 field_4 4), (F F2 field_5 5), (G G2 field_6 6)); +impl_tuple!(TupleBuilder8, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2), (D D2 field_3 3), (E E2 field_4 4), (F F2 field_5 5), (G G2 field_6 6), (H H2 field_7 7)); +impl_tuple!(TupleBuilder9, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2), (D D2 field_3 3), (E E2 field_4 4), (F F2 field_5 5), (G G2 field_6 6), (H H2 field_7 7), (I I2 field_8 8)); +impl_tuple!(TupleBuilder10, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2), (D D2 field_3 3), (E E2 field_4 4), (F F2 field_5 5), (G G2 field_6 6), (H H2 field_7 7), (I I2 field_8 8), (J J2 field_9 9)); +impl_tuple!(TupleBuilder11, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2), (D D2 field_3 3), (E E2 field_4 4), (F F2 field_5 5), (G G2 field_6 6), (H H2 field_7 7), (I I2 field_8 8), (J J2 field_9 9), (K K2 field_10 10)); +impl_tuple!(TupleBuilder12, (A A2 field_0 0), (B B2 field_1 1), (C C2 field_2 2), (D D2 field_3 3), (E E2 field_4 4), (F F2 field_5 5), (G G2 field_6 6), (H H2 field_7 7), (I I2 field_8 8), (J J2 field_9 9), (K K2 field_10 10), (L L2 field_11 11)); diff --git a/crates/fayalite/src/clock.rs b/crates/fayalite/src/clock.rs new file mode 100644 index 0000000..339d8ad --- /dev/null +++ b/crates/fayalite/src/clock.rs @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + expr::{Expr, ToExpr}, + int::{UInt, UIntType}, + intern::Interned, + reset::Reset, + source_location::SourceLocation, + ty::{ + impl_match_values_as_self, CanonicalType, CanonicalTypeKind, CanonicalValue, Connect, + DynCanonicalType, FixedType, Type, TypeEnum, Value, ValueEnum, + }, + util::interned_bit, +}; +use bitvec::slice::BitSlice; + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] +pub struct ClockType; + +impl ClockType { + pub const fn new() -> Self { + Self + } +} + +impl Type for ClockType { + type Value = Clock; + type CanonicalType = ClockType; + type CanonicalValue = Clock; + type MaskType = UIntType<1>; + type MaskValue = UInt<1>; + + impl_match_values_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + UIntType::new() + } + + fn type_enum(&self) -> TypeEnum { + TypeEnum::Clock(*self) + } + + fn from_canonical_type(t: Self::CanonicalType) -> Self { + t + } + + fn canonical(&self) -> Self::CanonicalType { + *self + } + + fn source_location(&self) -> SourceLocation { + SourceLocation::builtin() + } + + fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> { + Some(this) + } +} + +impl Connect for ClockType {} + +impl CanonicalType for ClockType { + const CANONICAL_TYPE_KIND: CanonicalTypeKind = CanonicalTypeKind::Clock; +} + +impl FixedType for ClockType { + fn fixed_type() -> Self { + Self + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct Clock(pub bool); + +impl ToExpr for Clock { + type Type = ClockType; + + fn ty(&self) -> Self::Type { + ClockType + } + + fn to_expr(&self) -> Expr { + Expr::from_value(self) + } +} + +impl Value for Clock { + fn to_canonical(&self) -> ::CanonicalValue { + *self + } + fn to_bits_impl(this: &Self) -> Interned { + interned_bit(this.0) + } +} + +impl CanonicalValue for Clock { + fn value_enum_impl(this: &Self) -> ValueEnum { + ValueEnum::Clock(*this) + } + fn to_bits_impl(this: &Self) -> Interned { + interned_bit(this.0) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, crate::Value)] +#[hdl(fixed_type, outline_generated)] +pub struct ClockDomain { + pub clock: Clock, + pub reset: Reset, +} + +pub trait ToClock { + fn to_clock(&self) -> Expr; +} + +impl ToClock for &'_ T { + fn to_clock(&self) -> Expr { + (**self).to_clock() + } +} + +impl ToClock for &'_ mut T { + fn to_clock(&self) -> Expr { + (**self).to_clock() + } +} + +impl ToClock for Box { + fn to_clock(&self) -> Expr { + (**self).to_clock() + } +} + +impl ToClock for Expr { + fn to_clock(&self) -> Expr { + *self + } +} + +impl ToClock for Clock { + fn to_clock(&self) -> Expr { + self.to_expr() + } +} + +impl ToClock for bool { + fn to_clock(&self) -> Expr { + self.to_expr().to_clock() + } +} + +impl ToClock for UInt<1> { + fn to_clock(&self) -> Expr { + self.to_expr().to_clock() + } +} diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs new file mode 100644 index 0000000..0699109 --- /dev/null +++ b/crates/fayalite/src/enum_.rs @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +#![allow(clippy::type_complexity)] +use crate::{ + bundle::{BundleValue, TypeHintTrait}, + expr::{ops::VariantAccess, Expr, ToExpr}, + int::{UInt, UIntType}, + intern::{Intern, Interned, MemoizeGeneric}, + module::{ + EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, ModuleBuilder, + NormalModule, Scope, + }, + source_location::SourceLocation, + ty::{ + CanonicalType, CanonicalTypeKind, CanonicalValue, Connect, DynCanonicalType, + DynCanonicalValue, DynType, MatchVariantAndInactiveScope, Type, TypeEnum, Value, ValueEnum, + }, +}; +use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; +use hashbrown::HashMap; +use std::{ + borrow::Cow, + fmt, + hash::{Hash, Hasher}, + iter::FusedIterator, + marker::PhantomData, +}; + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +pub struct VariantType { + pub name: Interned, + pub ty: Option, +} + +pub struct FmtDebugInEnum<'a, T>(&'a VariantType); + +impl fmt::Debug for FmtDebugInEnum<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let VariantType { name, ref ty } = *self.0; + if let Some(ty) = ty { + write!(f, "{name}({ty:?})") + } else { + write!(f, "{name}") + } + } +} + +impl fmt::Display for FmtDebugInEnum<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl VariantType { + pub fn map_opt_ty) -> Option>(self, f: F) -> VariantType { + let Self { name, ty } = self; + VariantType { name, ty: f(ty) } + } + pub fn map_ty U>(self, f: F) -> VariantType { + let Self { name, ty } = self; + VariantType { + name, + ty: ty.map(f), + } + } + pub fn as_ref_ty(&self) -> VariantType<&T> { + VariantType { + name: self.name, + ty: self.ty.as_ref(), + } + } + pub fn fmt_debug_in_enum(&self) -> FmtDebugInEnum { + FmtDebugInEnum(self) + } +} + +impl VariantType { + pub fn canonical(&self) -> VariantType { + self.as_ref_ty().map_ty(T::canonical) + } + pub fn to_dyn(&self) -> VariantType> { + self.as_ref_ty().map_ty(T::to_dyn) + } + pub fn canonical_dyn(&self) -> VariantType> { + self.as_ref_ty().map_ty(T::canonical_dyn) + } +} + +impl VariantType> { + pub fn from_canonical_type_helper_has_value(self, expected_name: &str) -> T { + assert_eq!(&*self.name, expected_name, "variant name doesn't match"); + let Some(ty) = self.ty else { + panic!("variant {expected_name} has no value but a value is expected"); + }; + T::from_dyn_canonical_type(ty) + } + pub fn from_canonical_type_helper_no_value(self, expected_name: &str) { + assert_eq!(&*self.name, expected_name, "variant name doesn't match"); + assert!( + self.ty.is_none(), + "variant {expected_name} has a value but is expected to have no value" + ); + } +} + +#[derive(Clone, Eq)] +struct DynEnumTypeImpl { + variants: Interned<[VariantType>]>, + name_indexes: HashMap, usize>, + bit_width: usize, + is_storable: bool, + is_castable_from_bits: bool, +} + +impl fmt::Debug for DynEnumTypeImpl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "DynEnumType ")?; + f.debug_set() + .entries( + self.variants + .iter() + .map(|variant| variant.fmt_debug_in_enum()), + ) + .finish() + } +} + +impl PartialEq for DynEnumTypeImpl { + fn eq(&self, other: &Self) -> bool { + self.variants == other.variants + } +} + +impl Hash for DynEnumTypeImpl { + fn hash(&self, state: &mut H) { + self.variants.hash(state); + } +} + +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct DynEnumType(Interned); + +impl fmt::Debug for DynEnumType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +fn discriminant_bit_width_impl(variant_count: usize) -> usize { + variant_count + .next_power_of_two() + .checked_ilog2() + .unwrap_or(0) as usize +} + +impl DynEnumType { + #[track_caller] + pub fn new(variants: Interned<[VariantType>]>) -> Self { + assert!(!variants.is_empty(), "zero-variant enums aren't yet supported: https://github.com/chipsalliance/firrtl-spec/issues/208"); + let mut name_indexes = HashMap::with_capacity(variants.len()); + let mut body_bit_width = 0usize; + let mut is_storable = true; + let mut is_castable_from_bits = true; + for (index, &VariantType { name, ty }) in variants.iter().enumerate() { + if let Some(old_index) = name_indexes.insert(name, index) { + panic!("duplicate variant name {name:?}: at both index {old_index} and {index}"); + } + if let Some(ty) = ty { + assert!( + ty.is_passive(), + "variant type must be a passive type: {ty:?}" + ); + body_bit_width = body_bit_width.max(ty.bit_width()); + is_storable &= ty.is_storable(); + is_castable_from_bits &= ty.is_castable_from_bits(); + } + } + let bit_width = body_bit_width + .checked_add(discriminant_bit_width_impl(variants.len())) + .unwrap_or_else(|| panic!("enum is too big: bit-width overflowed")); + Self( + DynEnumTypeImpl { + variants, + name_indexes, + bit_width, + is_storable, + is_castable_from_bits, + } + .intern_sized(), + ) + } + pub fn discriminant_bit_width(self) -> usize { + discriminant_bit_width_impl(self.variants().len()) + } + pub fn is_passive(self) -> bool { + true + } + pub fn is_storable(self) -> bool { + self.0.is_storable + } + pub fn is_castable_from_bits(self) -> bool { + self.0.is_castable_from_bits + } + pub fn bit_width(self) -> usize { + self.0.bit_width + } + pub fn name_indexes(&self) -> &HashMap, usize> { + &self.0.name_indexes + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct DynEnum { + ty: DynEnumType, + variant_index: usize, + variant_value: Option, +} + +impl DynEnum { + #[track_caller] + pub fn new_by_index( + ty: DynEnumType, + variant_index: usize, + variant_value: Option, + ) -> Self { + let variant = ty.variants()[variant_index]; + assert_eq!( + variant_value.as_ref().map(|v| v.ty()), + variant.ty, + "variant value doesn't match type" + ); + Self { + ty, + variant_index, + variant_value, + } + } + #[track_caller] + pub fn new_by_name( + ty: DynEnumType, + variant_name: Interned, + variant_value: Option, + ) -> Self { + let variant_index = ty.name_indexes()[&variant_name]; + Self::new_by_index(ty, variant_index, variant_value) + } + pub fn variant_index(&self) -> usize { + self.variant_index + } + pub fn variant_value(&self) -> &Option { + &self.variant_value + } + pub fn variant_with_type(&self) -> VariantType> { + self.ty.variants()[self.variant_index] + } + pub fn variant_name(&self) -> Interned { + self.variant_with_type().name + } + pub fn variant_type(&self) -> Option> { + self.variant_with_type().ty + } + pub fn variant_with_value(&self) -> VariantType<&DynCanonicalValue> { + self.variant_with_type() + .map_opt_ty(|_| self.variant_value.as_ref()) + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub struct VariantsHint { + pub known_variants: Interned<[VariantType>]>, + pub more_variants: bool, +} + +impl VariantsHint { + pub fn new( + known_variants: impl IntoIterator>>, + more_variants: bool, + ) -> Self { + let known_variants = Intern::intern_owned(Vec::from_iter(known_variants)); + Self { + known_variants, + more_variants, + } + } + pub fn check_variant( + self, + index: usize, + variant: VariantType<&dyn DynType>, + ) -> Result<(), String> { + let Some(&known_variant) = self.known_variants.get(index) else { + return if self.more_variants { + Ok(()) + } else { + Err(format!( + "too many variants: name={:?} index={index}", + variant.name + )) + }; + }; + let VariantType { + name: known_name, + ty: type_hint, + } = known_variant; + let VariantType { name, ty } = variant; + if name != known_name { + Err(format!( + "wrong variant name {name:?}, expected {known_name:?}" + )) + } else { + match (ty, type_hint) { + (Some(ty), Some(type_hint)) => type_hint.matches(ty), + (None, None) => Ok(()), + (None, Some(_)) => Err(format!( + "expected variant {name:?} to have type, no type provided" + )), + (Some(_), None) => Err(format!( + "expected variant {name:?} to have no type, but a type was provided" + )), + } + } + } +} + +pub trait EnumType: + Type< + CanonicalType = DynEnumType, + CanonicalValue = DynEnum, + MaskType = UIntType<1>, + MaskValue = UInt<1>, + MatchActiveScope = Scope, + MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope, + MatchVariantsIter = EnumMatchVariantsIter, + > + Connect +where + Self::Value: EnumValue + ToExpr, +{ + type Builder; + fn match_activate_scope( + v: Self::MatchVariantAndInactiveScope, + ) -> (Self::MatchVariant, Self::MatchActiveScope); + fn builder() -> Self::Builder; + fn variants(&self) -> Interned<[VariantType>]>; + fn variants_hint() -> VariantsHint; + #[allow(clippy::result_unit_err)] + fn variant_to_bits( + &self, + variant_index: usize, + variant_value: Option<&VariantValue>, + ) -> Result, ()> { + #[derive(Hash, Eq, PartialEq)] + struct VariantToBitsMemoize(PhantomData<(E, V)>); + impl Clone for VariantToBitsMemoize { + fn clone(&self) -> Self { + *self + } + } + impl Copy for VariantToBitsMemoize {} + impl< + E: EnumType>, + V: ToExpr + Eq + Hash + Send + Sync + 'static + Clone, + > MemoizeGeneric for VariantToBitsMemoize + { + type InputRef<'a> = (&'a E, usize, Option<&'a V>); + type InputOwned = (E, usize, Option); + type InputCow<'a> = (Cow<'a, E>, usize, Option>); + type Output = Result, ()>; + + fn input_borrow(input: &Self::InputOwned) -> Self::InputRef<'_> { + (&input.0, input.1, input.2.as_ref()) + } + fn input_eq(a: Self::InputRef<'_>, b: Self::InputRef<'_>) -> bool { + a == b + } + fn input_cow_into_owned(input: Self::InputCow<'_>) -> Self::InputOwned { + (input.0.into_owned(), input.1, input.2.map(Cow::into_owned)) + } + fn input_cow_borrow<'a>(input: &'a Self::InputCow<'_>) -> Self::InputRef<'a> { + (&input.0, input.1, input.2.as_deref()) + } + fn input_cow_from_owned<'a>(input: Self::InputOwned) -> Self::InputCow<'a> { + (Cow::Owned(input.0), input.1, input.2.map(Cow::Owned)) + } + fn input_cow_from_ref(input: Self::InputRef<'_>) -> Self::InputCow<'_> { + (Cow::Borrowed(input.0), input.1, input.2.map(Cow::Borrowed)) + } + fn inner(self, input: Self::InputRef<'_>) -> Self::Output { + let (ty, variant_index, variant_value) = input; + let ty = ty.canonical(); + let mut bits = BitVec::with_capacity(ty.bit_width()); + bits.extend_from_bitslice( + &variant_index.view_bits::()[..ty.discriminant_bit_width()], + ); + if let Some(variant_value) = variant_value { + bits.extend_from_bitslice(&variant_value.to_expr().to_literal_bits()?); + } + bits.resize(ty.bit_width(), false); + Ok(Intern::intern_owned(bits)) + } + } + VariantToBitsMemoize::(PhantomData).get(( + self, + variant_index, + variant_value, + )) + } +} + +pub trait EnumValue: Value +where + ::Type: EnumType, +{ +} + +pub struct EnumMatchVariantAndInactiveScope(EnumMatchVariantAndInactiveScopeImpl) +where + T::Value: EnumValue; + +impl MatchVariantAndInactiveScope for EnumMatchVariantAndInactiveScope +where + T::Value: EnumValue, +{ + type MatchVariant = T::MatchVariant; + type MatchActiveScope = Scope; + + fn match_activate_scope(self) -> (Self::MatchVariant, Self::MatchActiveScope) { + T::match_activate_scope(self) + } +} + +impl EnumMatchVariantAndInactiveScope +where + T::Value: EnumValue, +{ + pub fn variant_access(&self) -> Interned>> { + self.0.variant_access() + } + pub fn activate( + self, + ) -> ( + Interned>>, + Scope, + ) { + self.0.activate() + } +} + +#[derive(Clone)] +pub struct EnumMatchVariantsIter +where + T::Value: EnumValue, +{ + pub(crate) inner: EnumMatchVariantsIterImpl, + pub(crate) variant_index: std::ops::Range, +} + +impl Iterator for EnumMatchVariantsIter +where + T::Value: EnumValue, +{ + type Item = EnumMatchVariantAndInactiveScope; + + fn next(&mut self) -> Option { + self.variant_index.next().map(|variant_index| { + EnumMatchVariantAndInactiveScope(self.inner.for_variant_index(variant_index)) + }) + } + + fn size_hint(&self) -> (usize, Option) { + self.variant_index.size_hint() + } +} + +impl ExactSizeIterator for EnumMatchVariantsIter +where + T::Value: EnumValue, +{ + fn len(&self) -> usize { + self.variant_index.len() + } +} + +impl FusedIterator for EnumMatchVariantsIter where T::Value: EnumValue {} + +impl DoubleEndedIterator for EnumMatchVariantsIter +where + T::Value: EnumValue, +{ + fn next_back(&mut self) -> Option { + self.variant_index.next_back().map(|variant_index| { + EnumMatchVariantAndInactiveScope(self.inner.for_variant_index(variant_index)) + }) + } +} + +impl Type for DynEnumType { + type CanonicalType = DynEnumType; + type Value = DynEnum; + type CanonicalValue = DynEnum; + type MaskType = UIntType<1>; + type MaskValue = UInt<1>; + type MatchVariant = Option>; + type MatchActiveScope = Scope; + type MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope; + type MatchVariantsIter = EnumMatchVariantsIter; + + fn match_variants( + this: Expr, + module_builder: &mut ModuleBuilder, + source_location: SourceLocation, + ) -> Self::MatchVariantsIter + where + IO::Type: crate::bundle::BundleType, + { + module_builder.enum_match_variants_helper(this, source_location) + } + + fn mask_type(&self) -> Self::MaskType { + UIntType::new() + } + + fn canonical(&self) -> Self::CanonicalType { + *self + } + + fn source_location(&self) -> SourceLocation { + SourceLocation::builtin() + } + + fn type_enum(&self) -> TypeEnum { + TypeEnum::EnumType(*self) + } + + fn from_canonical_type(t: Self::CanonicalType) -> Self { + t + } + + fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> { + Some(this) + } +} + +impl Connect for DynEnumType {} + +pub struct NoBuilder; + +impl EnumType for DynEnumType { + type Builder = NoBuilder; + + fn match_activate_scope( + v: Self::MatchVariantAndInactiveScope, + ) -> (Self::MatchVariant, Self::MatchActiveScope) { + let (expr, scope) = v.0.activate(); + (expr.variant_type().ty.map(|_| expr.to_expr()), scope) + } + + fn builder() -> Self::Builder { + NoBuilder + } + + fn variants(&self) -> Interned<[VariantType>]> { + self.0.variants + } + + fn variants_hint() -> VariantsHint { + VariantsHint { + known_variants: [][..].intern(), + more_variants: true, + } + } +} + +impl CanonicalType for DynEnumType { + const CANONICAL_TYPE_KIND: CanonicalTypeKind = CanonicalTypeKind::EnumType; +} + +impl ToExpr for DynEnum { + type Type = DynEnumType; + + fn ty(&self) -> Self::Type { + self.ty + } + + fn to_expr(&self) -> Expr<::Value> { + Expr::from_value(self) + } +} + +impl Value for DynEnum { + fn to_canonical(&self) -> ::CanonicalValue { + self.clone() + } + fn to_bits_impl(this: &Self) -> Interned { + this.ty + .variant_to_bits(this.variant_index, this.variant_value.as_ref()) + .unwrap() + } +} + +impl EnumValue for DynEnum {} + +impl CanonicalValue for DynEnum { + fn value_enum_impl(this: &Self) -> ValueEnum { + ValueEnum::Enum(this.clone()) + } + fn to_bits_impl(this: &Self) -> Interned { + this.ty + .variant_to_bits(this.variant_index, this.variant_value.as_ref()) + .unwrap() + } +} + +mod impl_option { + #[allow(dead_code)] + #[derive(crate::Value)] + #[hdl(target(std::option::Option), connect_inexact, outline_generated)] + pub enum Option { + None, + Some(T), + } +} diff --git a/crates/fayalite/src/expr.rs b/crates/fayalite/src/expr.rs new file mode 100644 index 0000000..0ffaf53 --- /dev/null +++ b/crates/fayalite/src/expr.rs @@ -0,0 +1,1090 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + array::ArrayType, + bundle::{BundleType, BundleValue, DynBundle, DynBundleType, FieldType}, + enum_::{DynEnumType, EnumType, EnumValue}, + int::{DynSIntType, DynUIntType, FixedOrDynIntType, IntValue, UInt, UIntType}, + intern::{Intern, Interned, InternedCompare, PtrEqWithTypeId, SupportsPtrEqWithTypeId}, + memory::{DynPortType, MemPort, PortType}, + module::{ + transform::visit::{Fold, Folder, Visit, Visitor}, + Instance, ModuleIO, TargetName, + }, + reg::Reg, + source_location::SourceLocation, + ty::{ + DynCanonicalType, DynCanonicalValue, DynType, DynValue, DynValueTrait, Type, TypeWithDeref, + Value, + }, + util::ConstBool, + valueless::Valueless, + wire::Wire, +}; +use bitvec::slice::BitSlice; +use std::{any::Any, convert::Infallible, fmt, hash::Hash, marker::PhantomData, ops::Deref}; + +pub mod ops; + +macro_rules! expr_enum { + ( + pub enum $ExprEnum:ident { + $($Variant:ident($VariantTy:ty),)+ + } + ) => { + #[derive(Copy, Clone, Eq, PartialEq, Hash)] + pub enum $ExprEnum { + $($Variant(Interned<$VariantTy>),)+ + } + + impl fmt::Debug for $ExprEnum { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + $(Self::$Variant(v) => v.fmt(f),)+ + } + } + } + + impl $ExprEnum { + pub fn target(self) -> Option> { + match self { + $(Self::$Variant(v) => v.target(),)+ + } + } + #[allow(clippy::result_unit_err)] + pub fn to_literal_bits(&self) -> Result, ()> { + match self { + $(Self::$Variant(v) => v.to_literal_bits(),)+ + } + } + } + + impl ToExpr for $ExprEnum { + type Type = Interned; + + fn ty(&self) -> Self::Type { + match self { + $(Self::$Variant(v) => v.ty().canonical_dyn(),)+ + } + } + + fn to_expr(&self) -> Expr { + Expr::new_unchecked(*self) + } + } + + impl Fold for $ExprEnum { + fn fold(self, state: &mut State) -> Result { + state.fold_expr_enum(self) + } + fn default_fold(self, state: &mut State) -> Result { + match self { + $(Self::$Variant(v) => Fold::fold(v, state).map(Self::$Variant),)+ + } + } + } + + impl Visit for $ExprEnum { + fn visit(&self, state: &mut State) -> Result<(), State::Error> { + state.visit_expr_enum(self) + } + fn default_visit(&self, state: &mut State) -> Result<(), State::Error> { + match self { + $(Self::$Variant(v) => Visit::visit(v, state),)+ + } + } + } + }; +} + +expr_enum! { + pub enum ExprEnum { + Literal(Literal>), + ArrayLiteral(ops::ArrayLiteral>), + BundleLiteral(ops::BundleLiteral), + EnumLiteral(ops::EnumLiteral), + NotU(ops::Not), + NotS(ops::Not), + Neg(ops::Neg), + BitAndU(ops::BitAnd), + BitAndS(ops::BitAnd), + BitOrU(ops::BitOr), + BitOrS(ops::BitOr), + BitXorU(ops::BitXor), + BitXorS(ops::BitXor), + AddU(ops::Add), + AddS(ops::Add), + SubU(ops::Sub), + SubS(ops::Sub), + MulU(ops::Mul), + MulS(ops::Mul), + DynShlU(ops::DynShl), + DynShlS(ops::DynShl), + DynShrU(ops::DynShr), + DynShrS(ops::DynShr), + FixedShlU(ops::FixedShl), + FixedShlS(ops::FixedShl), + FixedShrU(ops::FixedShr), + FixedShrS(ops::FixedShr), + CmpLtU(ops::CmpLt), + CmpLtS(ops::CmpLt), + CmpLeU(ops::CmpLe), + CmpLeS(ops::CmpLe), + CmpGtU(ops::CmpGt), + CmpGtS(ops::CmpGt), + CmpGeU(ops::CmpGe), + CmpGeS(ops::CmpGe), + CmpEqU(ops::CmpEq), + CmpEqS(ops::CmpEq), + CmpNeU(ops::CmpNe), + CmpNeS(ops::CmpNe), + CastUIntToUInt(ops::CastInt), + CastUIntToSInt(ops::CastInt), + CastSIntToUInt(ops::CastInt), + CastSIntToSInt(ops::CastInt), + SliceUInt(ops::Slice), + SliceSInt(ops::Slice), + ReduceBitAnd(ops::ReduceBitAnd>), + ReduceBitOr(ops::ReduceBitOr>), + ReduceBitXor(ops::ReduceBitXor>), + FieldAccess(ops::FieldAccess>), + VariantAccess(ops::VariantAccess>), + ArrayIndex(ops::ArrayIndex>), + DynArrayIndex(ops::DynArrayIndex>), + CastToBits(ops::CastToBits), + CastBitsTo(ops::CastBitsTo>), + CastBitToClock(ops::CastBitToClock), + CastBitToSyncReset(ops::CastBitToSyncReset), + CastBitToAsyncReset(ops::CastBitToAsyncReset), + CastSyncResetToReset(ops::CastSyncResetToReset), + CastAsyncResetToReset(ops::CastAsyncResetToReset), + CastClockToBit(ops::CastClockToBit), + CastSyncResetToBit(ops::CastSyncResetToBit), + CastAsyncResetToBit(ops::CastAsyncResetToBit), + CastResetToBit(ops::CastResetToBit), + ModuleIO(ModuleIO>), + Instance(Instance), + Wire(Wire>), + Reg(Reg>), + MemPort(MemPort), + } +} + +pub struct Expr { + /// use weird names to help work around rust-analyzer bug + __enum: ExprEnum, + __phantom: PhantomData, +} + +impl Expr { + pub fn expr_enum(self) -> ExprEnum { + self.__enum + } + pub fn new_unchecked(expr_enum: ExprEnum) -> Self { + Self { + __enum: expr_enum, + __phantom: PhantomData, + } + } + pub fn canonical(self) -> Expr<::CanonicalValue> + where + T: Value, + { + Expr { + __enum: self.expr_enum(), + __phantom: PhantomData, + } + } + pub fn dyn_canonical_type(self) -> Interned { + self.expr_enum().ty() + } + pub fn canonical_type(self) -> ::CanonicalType + where + T: Value, + { + ::CanonicalType::from_dyn_canonical_type(self.dyn_canonical_type()) + } + pub fn valueless(self) -> Valueless + where + T: Value>, + { + Valueless { ty: self.ty() } + } + #[allow(clippy::result_unit_err)] + pub fn to_literal_bits(&self) -> Result, ()> { + self.expr_enum().to_literal_bits() + } + #[track_caller] + pub fn with_type>>(self) -> Expr + where + T: Value>, + { + let retval = Expr::::new_unchecked(self.expr_enum()); + let _ = retval.ty(); // check that the type is correct + retval + } + pub fn to_dyn(self) -> Expr { + Expr::new_unchecked(self.expr_enum()) + } + pub fn to_canonical_dyn(self) -> Expr { + Expr::new_unchecked(self.expr_enum()) + } + #[track_caller] + pub fn from_value(value: &T) -> Self + where + T: Value>, + { + Literal::::new_unchecked(value.to_canonical()).to_expr() + } + pub fn target(self) -> Option> { + self.expr_enum().target() + } + pub fn flow(self) -> Flow { + self.target().map(|v| v.flow()).unwrap_or(Flow::Source) + } +} + +impl Copy for Expr {} + +impl Clone for Expr { + fn clone(&self) -> Self { + *self + } +} + +impl fmt::Debug for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + __enum, + __phantom: _, + } = self; + __enum.fmt(f) + } +} + +impl>> sealed::Sealed for Expr {} + +impl PartialEq for Expr { + fn eq(&self, other: &Self) -> bool { + let Self { + __enum, + __phantom: _, + } = self; + *__enum == other.__enum + } +} + +impl Eq for Expr {} + +impl Hash for Expr { + fn hash(&self, state: &mut H) { + let Self { + __enum, + __phantom: _, + } = self; + __enum.hash(state); + } +} + +impl Expr> +where + T: FixedOrDynIntType<1, Signed = ConstBool>, +{ + pub fn as_bool(self) -> Expr> { + assert_eq!(self.canonical_type().width, 1); + Expr::new_unchecked(self.expr_enum()) + } +} + +impl>> Expr { + pub fn field>>( + self, + name: &str, + ) -> Expr { + ops::FieldAccess::::new_unchecked( + self.canonical(), + name.intern(), + ) + .to_expr() + } +} + +impl>> ToExpr for Expr { + type Type = T::Type; + + fn ty(&self) -> T::Type { + T::Type::from_dyn_canonical_type(self.dyn_canonical_type()) + } + + fn to_expr(&self) -> Expr { + *self + } +} + +impl, T> Deref for Expr +where + T: TypeWithDeref, +{ + type Target = T::MatchVariant; + + fn deref(&self) -> &Self::Target { + T::expr_deref(self) + } +} + +impl>, State: ?Sized + Folder> Fold for Expr { + fn fold(self, state: &mut State) -> Result { + state.fold_expr(self) + } + fn default_fold(self, state: &mut State) -> Result { + Ok(Expr::::new_unchecked(self.expr_enum().fold(state)?).with_type()) + } +} + +impl>, State: ?Sized + Visitor> Visit for Expr { + fn visit(&self, state: &mut State) -> Result<(), State::Error> { + state.visit_expr(self) + } + fn default_visit(&self, state: &mut State) -> Result<(), State::Error> { + self.expr_enum().visit(state) + } +} + +pub trait ExprTraitBase: + fmt::Debug + Any + SupportsPtrEqWithTypeId + Send + Sync + sealed::Sealed + ToExpr +{ +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum Flow { + Source, + Sink, + Duplex, +} + +impl Flow { + pub const fn flip(self) -> Flow { + match self { + Flow::Source => Flow::Sink, + Flow::Sink => Flow::Source, + Flow::Duplex => Flow::Duplex, + } + } + pub const fn flip_if(self, flipped: bool) -> Flow { + if flipped { + self.flip() + } else { + self + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TargetPathBundleField { + pub name: Interned, +} + +impl fmt::Display for TargetPathBundleField { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, ".{}", self.name) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TargetPathArrayElement { + pub index: usize, +} + +impl fmt::Display for TargetPathArrayElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[{}]", self.index) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TargetPathDynArrayElement {} + +impl fmt::Display for TargetPathDynArrayElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[]") + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TargetPathElement { + BundleField(TargetPathBundleField), + ArrayElement(TargetPathArrayElement), + DynArrayElement(TargetPathDynArrayElement), +} + +impl From for TargetPathElement { + fn from(value: TargetPathBundleField) -> Self { + Self::BundleField(value) + } +} + +impl From for TargetPathElement { + fn from(value: TargetPathArrayElement) -> Self { + Self::ArrayElement(value) + } +} + +impl From for TargetPathElement { + fn from(value: TargetPathDynArrayElement) -> Self { + Self::DynArrayElement(value) + } +} + +impl fmt::Display for TargetPathElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::BundleField(v) => v.fmt(f), + Self::ArrayElement(v) => v.fmt(f), + Self::DynArrayElement(v) => v.fmt(f), + } + } +} + +impl TargetPathElement { + pub fn canonical_ty(&self, parent: Interned) -> Interned { + match self { + &Self::BundleField(TargetPathBundleField { name }) => { + let parent_ty = parent + .canonical_ty() + .type_enum() + .bundle_type() + .expect("parent type is known to be a bundle"); + let field = parent_ty + .field_by_name(name) + .expect("field name is known to be a valid field of parent type"); + field.ty + } + &Self::ArrayElement(TargetPathArrayElement { index }) => { + let parent_ty = parent + .canonical_ty() + .type_enum() + .array_type() + .expect("parent type is known to be an array"); + assert!(index < parent_ty.len()); + *parent_ty.element() + } + Self::DynArrayElement(_) => { + let parent_ty = parent + .canonical_ty() + .type_enum() + .array_type() + .expect("parent type is known to be an array"); + *parent_ty.element() + } + } + } + pub fn flow(&self, parent: Interned) -> Flow { + match self { + Self::BundleField(v) => { + let parent_ty = parent + .canonical_ty() + .type_enum() + .bundle_type() + .expect("parent type is known to be a bundle"); + let field = parent_ty + .field_by_name(v.name) + .expect("field name is known to be a valid field of parent type"); + parent.flow().flip_if(field.flipped) + } + Self::ArrayElement(_) => parent.flow(), + Self::DynArrayElement(_) => parent.flow(), + } + } + pub fn is_static(&self) -> bool { + match self { + Self::BundleField(_) | Self::ArrayElement(_) => true, + Self::DynArrayElement(_) => false, + } + } +} + +macro_rules! impl_target_base { + ( + $(#[$enum_meta:meta])* + $enum_vis:vis enum $TargetBase:ident { + $( + #[is = $is_fn:ident] + #[to = $to_fn:ident] + $(#[$variant_meta:meta])* + $Variant:ident($VariantTy:ty), + )* + } + ) => { + $(#[$enum_meta])* + $enum_vis enum $TargetBase { + $( + $(#[$variant_meta])* + $Variant($VariantTy), + )* + } + + impl fmt::Debug for $TargetBase { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + $(Self::$Variant(v) => v.fmt(f),)* + } + } + } + + $( + impl From<$VariantTy> for $TargetBase { + fn from(value: $VariantTy) -> Self { + Self::$Variant(value) + } + } + + impl From<$VariantTy> for Target { + fn from(value: $VariantTy) -> Self { + $TargetBase::$Variant(value).into() + } + } + )* + + impl $TargetBase { + $( + $enum_vis fn $is_fn(&self) -> bool { + self.$to_fn().is_some() + } + $enum_vis fn $to_fn(&self) -> Option<&$VariantTy> { + if let Self::$Variant(retval) = self { + Some(retval) + } else { + None + } + } + )* + $enum_vis fn must_connect_to(&self) -> bool { + match self { + $(Self::$Variant(v) => v.must_connect_to(),)* + } + } + $enum_vis fn flow(&self) -> Flow { + match self { + $(Self::$Variant(v) => v.flow(),)* + } + } + $enum_vis fn source_location(&self) -> SourceLocation { + match self { + $(Self::$Variant(v) => v.source_location(),)* + } + } + } + }; +} + +impl_target_base! { + #[derive(Clone, PartialEq, Eq, Hash)] + pub enum TargetBase { + #[is = is_module_io] + #[to = module_io] + ModuleIO(ModuleIO>), + #[is = is_mem_port] + #[to = mem_port] + MemPort(MemPort), + #[is = is_reg] + #[to = reg] + Reg(Reg>), + #[is = is_wire] + #[to = wire] + Wire(Wire>), + #[is = is_instance] + #[to = instance] + Instance(Instance), + } +} + +impl fmt::Display for TargetBase { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.target_name()) + } +} + +impl TargetBase { + pub fn target_name(&self) -> TargetName { + match self { + TargetBase::ModuleIO(v) => TargetName(v.scoped_name(), None), + TargetBase::MemPort(v) => TargetName(v.mem_name(), Some(v.port_name())), + TargetBase::Reg(v) => TargetName(v.scoped_name(), None), + TargetBase::Wire(v) => TargetName(v.scoped_name(), None), + TargetBase::Instance(v) => TargetName(v.scoped_name(), None), + } + } + pub fn canonical_ty(&self) -> Interned { + match self { + TargetBase::ModuleIO(v) => v.ty(), + TargetBase::MemPort(v) => v.ty().canonical_dyn(), + TargetBase::Reg(v) => v.ty(), + TargetBase::Wire(v) => v.ty(), + TargetBase::Instance(v) => v.ty().canonical_dyn(), + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct TargetChild { + parent: Interned, + path_element: Interned, + canonical_ty: Interned, + flow: Flow, +} + +impl fmt::Debug for TargetChild { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + parent, + path_element, + canonical_ty: _, + flow: _, + } = self; + parent.fmt(f)?; + fmt::Display::fmt(path_element, f) + } +} + +impl fmt::Display for TargetChild { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + parent, + path_element, + canonical_ty: _, + flow: _, + } = self; + parent.fmt(f)?; + path_element.fmt(f) + } +} + +impl TargetChild { + pub fn new(parent: Interned, path_element: Interned) -> Self { + Self { + parent, + path_element, + canonical_ty: path_element.canonical_ty(parent), + flow: path_element.flow(parent), + } + } + pub fn parent(self) -> Interned { + self.parent + } + pub fn path_element(self) -> Interned { + self.path_element + } + pub fn canonical_ty(self) -> Interned { + self.canonical_ty + } + pub fn flow(self) -> Flow { + self.flow + } + pub fn bundle_field(self) -> Option>> { + if let TargetPathElement::BundleField(TargetPathBundleField { name }) = *self.path_element { + let parent_ty = self + .parent + .canonical_ty() + .type_enum() + .bundle_type() + .expect("parent known to be bundle"); + Some( + parent_ty + .field_by_name(name) + .expect("field name known to be a valid field of parent"), + ) + } else { + None + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub enum Target { + Base(Interned), + Child(TargetChild), +} + +impl From for Target { + fn from(value: TargetBase) -> Self { + Self::Base(Intern::intern_sized(value)) + } +} + +impl From for Target { + fn from(value: TargetChild) -> Self { + Self::Child(value) + } +} + +impl From> for Target { + fn from(value: Interned) -> Self { + Self::Base(value) + } +} + +impl Target { + pub fn base(&self) -> Interned { + let mut target = self; + loop { + match target { + Self::Base(v) => break *v, + Self::Child(v) => target = &v.parent, + } + } + } + pub fn child(&self) -> Option { + match *self { + Target::Base(_) => None, + Target::Child(v) => Some(v), + } + } + pub fn is_static(&self) -> bool { + let mut target = self; + loop { + match target { + Self::Base(_) => return true, + Self::Child(v) if !v.path_element().is_static() => return false, + Self::Child(v) => target = &v.parent, + } + } + } + #[must_use] + pub fn join(&self, path_element: Interned) -> Self { + TargetChild::new(self.intern(), path_element).into() + } + pub fn flow(&self) -> Flow { + match self { + Self::Base(v) => v.flow(), + Self::Child(v) => v.flow(), + } + } + pub fn canonical_ty(&self) -> Interned { + match self { + Target::Base(v) => v.canonical_ty(), + Target::Child(v) => v.canonical_ty(), + } + } +} + +impl fmt::Display for Target { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Base(v) => v.fmt(f), + Self::Child(v) => v.fmt(f), + } + } +} + +impl fmt::Debug for Target { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Base(v) => v.fmt(f), + Self::Child(v) => v.fmt(f), + } + } +} + +pub trait ExprTrait: ExprTraitBase { + fn expr_enum(&self) -> ExprEnum; + fn target(&self) -> Option>; + fn valueless(&self) -> Valueless { + Valueless { ty: self.ty() } + } + #[allow(clippy::result_unit_err)] + fn to_literal_bits(&self) -> Result, ()>; +} + +impl ExprTraitBase for T {} + +impl InternedCompare for dyn ExprTrait { + type InternedCompareKey = PtrEqWithTypeId; + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + Self::get_ptr_eq_with_type_id(this) + } + fn interned_compare_key_weak(this: &std::sync::Weak) -> Self::InternedCompareKey { + Self::get_ptr_eq_with_type_id(&*this.upgrade().unwrap()) + } +} + +pub struct SimState {} + +mod sealed { + pub trait Sealed {} +} + +pub trait ToExpr { + type Type: Type; + fn ty(&self) -> Self::Type; + fn to_expr(&self) -> Expr<::Value>; +} + +impl ToExpr for &'_ T { + type Type = T::Type; + + fn ty(&self) -> Self::Type { + (**self).ty() + } + + fn to_expr(&self) -> Expr<::Value> { + (**self).to_expr() + } +} + +impl ToExpr for &'_ mut T { + type Type = T::Type; + + fn ty(&self) -> Self::Type { + (**self).ty() + } + + fn to_expr(&self) -> Expr<::Value> { + (**self).to_expr() + } +} + +impl ToExpr for Box { + type Type = T::Type; + + fn ty(&self) -> Self::Type { + (**self).ty() + } + + fn to_expr(&self) -> Expr<::Value> { + (**self).to_expr() + } +} + +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct Literal { + value: T::CanonicalValue, +} + +impl fmt::Debug for Literal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.value.fmt(f) + } +} + +impl Literal { + #[track_caller] + pub fn new_unchecked(value: T::CanonicalValue) -> Self { + assert!( + value.ty().is_passive(), + "can't have a literal with flipped fields" + ); + Self { value } + } + pub fn value(&self) -> &T::CanonicalValue { + &self.value + } + pub fn canonical(&self) -> Literal { + Literal { + value: self.value.clone(), + } + } +} + +impl sealed::Sealed for Literal {} + +impl ToExpr for Literal { + type Type = T; + + fn ty(&self) -> Self::Type { + Self::Type::from_canonical_type(self.value.ty()) + } + + fn to_expr(&self) -> Expr<::Value> { + Expr::new_unchecked(self.expr_enum()) + } +} + +impl ExprTrait for Literal { + fn expr_enum(&self) -> ExprEnum { + ExprEnum::Literal( + Literal { + value: self.value.to_canonical_dyn(), + } + .intern_sized(), + ) + } + + fn target(&self) -> Option> { + None + } + + fn to_literal_bits(&self) -> Result, ()> { + Ok(self.value.to_bits()) + } +} + +impl Fold for Literal +where + T::CanonicalValue: Fold, +{ + fn fold(self, state: &mut State) -> Result { + state.fold_literal(self) + } + fn default_fold(self, state: &mut State) -> Result { + Ok(Literal { + value: self.value.fold(state)?, + }) + } +} + +impl Visit for Literal +where + T::CanonicalValue: Visit, +{ + fn visit(&self, state: &mut State) -> Result<(), ::Error> { + state.visit_literal(self) + } + fn default_visit(&self, state: &mut State) -> Result<(), ::Error> { + self.value.visit(state) + } +} + +impl sealed::Sealed for ModuleIO {} + +impl ToExpr for ModuleIO { + type Type = T; + + fn ty(&self) -> Self::Type { + self.field_type().ty.clone() + } + + fn to_expr(&self) -> Expr<::Value> { + Expr::new_unchecked(self.expr_enum()) + } +} + +impl ExprTrait for ModuleIO { + fn expr_enum(&self) -> ExprEnum { + ExprEnum::ModuleIO(self.to_canonical_dyn_module_io().intern_sized()) + } + + fn target(&self) -> Option> { + Some(Intern::intern_sized( + self.to_canonical_dyn_module_io().into(), + )) + } + + fn to_literal_bits(&self) -> Result, ()> { + Err(()) + } +} + +impl sealed::Sealed for Instance where T::Type: BundleType {} + +impl ToExpr for Instance +where + T::Type: BundleType, +{ + type Type = T::Type; + + fn ty(&self) -> Self::Type { + (*self.instantiated().io_ty()).clone() + } + + fn to_expr(&self) -> Expr<::Value> { + Expr::new_unchecked(self.expr_enum()) + } +} + +impl ExprTrait for Instance +where + T::Type: BundleType, +{ + fn expr_enum(&self) -> ExprEnum { + ExprEnum::Instance(self.canonical().intern_sized()) + } + + fn target(&self) -> Option> { + Some(Intern::intern_sized(self.canonical().into())) + } + + fn to_literal_bits(&self) -> Result, ()> { + Err(()) + } +} + +impl sealed::Sealed for Wire {} + +impl ExprTrait for Wire { + fn expr_enum(&self) -> ExprEnum { + ExprEnum::Wire(self.to_dyn_canonical_wire().intern_sized()) + } + + fn target(&self) -> Option> { + Some(Intern::intern_sized(self.to_dyn_canonical_wire().into())) + } + + fn to_literal_bits(&self) -> Result, ()> { + Err(()) + } +} + +impl sealed::Sealed for Reg {} + +impl ExprTrait for Reg { + fn expr_enum(&self) -> ExprEnum { + ExprEnum::Reg(self.to_dyn_canonical_reg().intern_sized()) + } + + fn target(&self) -> Option> { + Some(Intern::intern_sized(self.to_dyn_canonical_reg().into())) + } + + fn to_literal_bits(&self) -> Result, ()> { + Err(()) + } +} + +#[doc(hidden)] +pub fn value_from_expr_type(_expr: Expr, infallible: Infallible) -> V { + match infallible {} +} + +#[doc(hidden)] +pub fn check_match_expr(_expr: Expr, _check_fn: impl FnOnce(V, Infallible)) {} + +#[doc(hidden)] +#[inline] +pub fn make_enum_expr( + _check_fn: impl FnOnce(Infallible) -> V, + build: impl FnOnce(::Builder) -> Expr, +) -> Expr +where + V::Type: EnumType, +{ + build(V::Type::builder()) +} + +#[doc(hidden)] +#[inline] +pub fn make_bundle_expr( + _check_fn: impl FnOnce(Infallible) -> V, + build: impl FnOnce(::Builder) -> Expr, +) -> Expr +where + V::Type: BundleType, +{ + build(V::Type::builder()) +} + +impl sealed::Sealed for MemPort where Self: ToExpr {} + +impl ExprTrait for MemPort +where + Self: ToExpr, +{ + fn expr_enum(&self) -> ExprEnum { + ExprEnum::MemPort(self.canonical().intern_sized()) + } + fn target(&self) -> Option> { + Some(Intern::intern_sized(self.canonical().into())) + } + fn to_literal_bits(&self) -> Result, ()> { + Err(()) + } +} diff --git a/crates/fayalite/src/expr/ops.rs b/crates/fayalite/src/expr/ops.rs new file mode 100644 index 0000000..a2c242d --- /dev/null +++ b/crates/fayalite/src/expr/ops.rs @@ -0,0 +1,1590 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::{ + array::{Array, ArrayType, ArrayTypeTrait, ValueArrayOrSlice}, + bundle::{BundleType, BundleValue, DynBundleType, FieldType}, + clock::{Clock, ClockType, ToClock}, + enum_::{DynEnumType, EnumType, EnumValue, VariantType}, + expr::{ + sealed, Expr, ExprEnum, ExprTrait, Target, TargetPathArrayElement, TargetPathBundleField, + TargetPathDynArrayElement, TargetPathElement, ToExpr, + }, + int::{ + DynIntType, DynSInt, DynSIntType, DynUInt, DynUIntType, FixedOrDynIntType, Int, IntCmp, + IntTypeTrait, IntValue, UInt, UIntType, + }, + intern::{Intern, Interned}, + reset::{ + AsyncReset, AsyncResetType, Reset, ResetType, SyncReset, SyncResetType, ToAsyncReset, + ToReset, ToSyncReset, + }, + ty::{ + CanonicalType, CanonicalValue, DynCanonicalType, DynCanonicalValue, DynType, DynValueTrait, + Type, Value, + }, + util::{interned_bit, ConstBool, ConstBoolDispatch, ConstBoolDispatchTag, GenericConstBool}, + valueless::{Valueless, ValuelessTr}, +}; +use bitvec::{slice::BitSlice, vec::BitVec}; +use num_traits::ToPrimitive; +use std::{ + fmt, + hash::{Hash, Hasher}, + ops::{self, Index, Range, RangeBounds}, +}; + +macro_rules! fixed_ary_op { + ( + pub struct $name:ident<$($T:ident,)* $(#[const] $C:ident: $CTy:ty,)*> + where + ($($where:tt)*) + { + $($arg_vis:vis $arg:ident: $Arg:ty,)+ + $(#[cache] + $cache_before_ty:ident: $CacheBeforeTy:ty = $cache_before_ty_expr:expr,)* + #[type$(($ty_arg:ident))?] + $ty_vis:vis $ty_name:ident: $Ty:ty = $ty_expr:expr, + $(#[cache] + $cache_after_ty:ident: $CacheAfterTy:ty = $cache_after_ty_expr:expr,)* + $(#[target] + $target_name:ident: Option> = $target_expr:expr,)? + fn simulate(&$simulate_self:ident, $sim_state:ident: &mut SimState) -> _ { + $($simulate_body:tt)+ + } + + fn expr_enum(&$expr_enum_self:ident) -> _ { + $($expr_enum_body:tt)+ + } + + fn to_literal_bits(&$to_literal_bits_self:ident) -> Result, ()> { + $($to_literal_bits_body:tt)+ + } + } + ) => { + pub struct $name<$($T,)* $(const $C: $CTy,)*> + where + $($where)* + { + $($arg_vis $arg: $Arg,)+ + $($cache_before_ty: $CacheBeforeTy,)* + $ty_vis $ty_name: $Ty, + $($cache_after_ty: $CacheAfterTy,)* + $($target_name: Option>,)? + } + + impl<$($T,)* $(const $C: $CTy,)*> $name<$($T,)* $($C,)*> + where + $($where)* + { + pub fn new_unchecked($($arg: $Arg,)* $($ty_arg: $Ty,)?) -> Self { + $(let $cache_before_ty: $CacheBeforeTy = $cache_before_ty_expr;)* + let $ty_name: $Ty = $ty_expr; + $(let $cache_after_ty: $CacheAfterTy = $cache_after_ty_expr;)* + $(let $target_name: Option> = $target_expr;)? + Self { + $($arg,)* + $($cache_before_ty,)* + $ty_name, + $($cache_after_ty,)* + $($target_name,)? + } + } + pub fn canonical(&self) -> $name<$($T::CanonicalType,)* $($C,)*> { + $name { + $($arg: self.$arg.clone(),)* + $($cache_before_ty: self.$cache_before_ty.clone(),)* + $ty_name: self.$ty_name.canonical(), + $($cache_after_ty: self.$cache_after_ty.clone(),)* + $($target_name: self.$target_name,)? + } + } + } + + impl<$($T,)* $(const $C: $CTy,)*> Clone for $name<$($T,)* $($C,)*> + where + $($where)* + { + fn clone(&self) -> Self { + Self { + $($arg: self.$arg.clone(),)* + $($cache_before_ty: self.$cache_before_ty.clone(),)* + $ty_name: self.$ty_name.clone(), + $($cache_after_ty: self.$cache_after_ty.clone(),)* + $($target_name: self.$target_name,)? + } + } + } + + impl<$($T,)* $(const $C: $CTy,)*> PartialEq for $name<$($T,)* $($C,)*> + where + $($where)* + { + fn eq(&self, rhs: &Self) -> bool { + $(self.$arg == rhs.$arg &&)* self.$ty_name == rhs.$ty_name + } + } + + impl<$($T,)* $(const $C: $CTy,)*> Eq for $name<$($T,)* $($C,)*> + where + $($where)* + { + } + + impl<$($T,)* $(const $C: $CTy,)*> Hash for $name<$($T,)* $($C,)*> + where + $($where)* + { + fn hash(&self, state: &mut H) { + $(self.$arg.hash(state);)* + self.$ty_name.hash(state); + } + } + + impl<$($T,)* $(const $C: $CTy,)*> fmt::Debug for $name<$($T,)* $($C,)*> + where + $($where)* + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_tuple(stringify!($name))$(.field(&self.$arg))*.finish() + } + } + + impl<$($T,)* $(const $C: $CTy,)*> ToExpr for $name<$($T,)* $($C,)*> + where + $($where)* + { + type Type = $Ty; + + fn ty(&self) -> Self::Type { + self.$ty_name.clone() + } + fn to_expr(&self) -> Expr<::Value> { + Expr::new_unchecked(self.expr_enum()) + } + } + + impl<$($T,)* $(const $C: $CTy,)*> ExprTrait for $name<$($T,)* $($C,)*> + where + $($where)* + { + fn expr_enum(&$expr_enum_self) -> ExprEnum { + $($expr_enum_body)+ + } + fn target(&self) -> Option> { + ($(self.$target_name,)? None::>,).0 + } + fn to_literal_bits(&$to_literal_bits_self) -> Result, ()> { + $($to_literal_bits_body)+ + } + } + + impl<$($T,)* $(const $C: $CTy,)*> sealed::Sealed for $name<$($T,)* $($C,)*> + where + $($where)* + { + } + }; +} + +macro_rules! unary_op { + ( + #[method = $op:ident] + impl<$T:ident> $Op:ident for _ where ($($where:tt)*) { + fn expr_enum(&$expr_enum_self:ident) -> _ { + $($expr_enum_body:tt)+ + } + } + ) => { + fixed_ary_op! { + pub struct $Op<$T,> + where ( + $($where)* + ) + { + pub arg: Expr<$T::CanonicalValue>, + #[type] + ty: < as ops::$Op>::Output as ValuelessTr>::Type = ops::$Op::$op( + Valueless::<$T>::from_canonical(arg.valueless()), + ).ty, + #[cache] + literal_bits: Result, ()> = { + arg.to_literal_bits().map(|v| ops::$Op::$op($T::CanonicalValue::from_bit_slice(&v)).to_bits()) + }, + + fn simulate(&self, sim_state: &mut SimState) -> _ { + ops::$Op::$op(self.arg.simulate(sim_state)) + } + + fn expr_enum(&$expr_enum_self) -> _ { + $($expr_enum_body)+ + } + + fn to_literal_bits(&self) -> Result, ()> { + self.literal_bits + } + } + } + + impl<$T, V: Value> ops::$Op for Expr + where + $($where)* + { + type Output = Expr<< as ops::$Op>::Output as ValuelessTr>::Value>; + + fn $op(self) -> Self::Output { + $Op::<$T>::new_unchecked(self.canonical()).to_expr() + } + } + }; +} + +unary_op! { + #[method = not] + impl Not for _ + where ( + T: IntTypeTrait, + ) + { + fn expr_enum(&self) -> _ { + struct Tag; + impl ConstBoolDispatchTag for Tag { + type Type = Not>; + } + match ConstBoolDispatch::new::(self.canonical()) { + ConstBoolDispatch::False(v) => ExprEnum::NotU(v.intern_sized()), + ConstBoolDispatch::True(v) => ExprEnum::NotS(v.intern_sized()), + } + } + } +} + +unary_op! { + #[method = neg] + impl Neg for _ + where ( + T: IntTypeTrait>, + ) + { + fn expr_enum(&self) -> _ { + ExprEnum::Neg(self.canonical().intern_sized()) + } + } +} + +macro_rules! binary_op { + ( + #[method = $op:ident, rhs_to_canonical_dyn = $rhs_to_canonical_dyn:ident, expr_enum_u = $expr_enum_u:ident, expr_enum_s = $expr_enum_s:ident] + impl<$LhsType:ident, $RhsType:ident> $Op:ident for _ where $($where:tt)* + ) => { + fixed_ary_op! { + pub struct $Op<$LhsType, $RhsType,> + where ( + $($where)* + ) + { + pub lhs: Expr<$LhsType::CanonicalValue>, + pub rhs: Expr<$RhsType::CanonicalValue>, + #[type] + ty: < as ops::$Op>>::Output as ValuelessTr>::Type = ops::$Op::$op( + Valueless::<$LhsType>::from_canonical(lhs.valueless()), + Valueless::<$RhsType>::from_canonical(rhs.valueless()), + ).ty, + #[cache] + literal_bits: Result, ()> = { + lhs.to_literal_bits() + .ok() + .zip(rhs.to_literal_bits().ok()) + .map(|(lhs, rhs)| { + ops::$Op::$op( + $LhsType::CanonicalValue::from_bit_slice(&lhs), + $RhsType::CanonicalValue::from_bit_slice(&rhs), + ) + .to_bits() + }) + .ok_or(()) + }, + fn simulate(&self, sim_state: &mut SimState) -> _ { + ops::$Op::$op(self.lhs.simulate(sim_state), self.rhs.simulate(sim_state)) + } + fn expr_enum(&self) -> _ { + struct Tag; + impl ConstBoolDispatchTag for Tag { + type Type = $Op, DynIntType, DynUIntType>; + } + match ConstBoolDispatch::new::(self.canonical()) { + ConstBoolDispatch::False(v) => ExprEnum::$CmpOpU(v.intern_sized()), + ConstBoolDispatch::True(v) => ExprEnum::$CmpOpS(v.intern_sized()), + } + } + + fn to_literal_bits(&self) -> Result, ()> { + self.literal_bits + } + } + })* + + impl, Rhs: Value, Lhs: Value> IntCmp> for Expr { + type Output = Expr>; + + $(fn $fn(self, rhs: Expr) -> Self::Output { + $CmpOp::>::new_unchecked(self.canonical(), rhs.canonical()).to_expr() + })* + } + + impl, Rhs: Value, Lhs: Value> IntCmp for Expr { + type Output = Expr>; + + $(fn $fn(self, rhs: Rhs) -> Self::Output { + self.$fn(rhs.to_expr()) + })* + } + + impl, Rhs: Value> IntCmp> for IntValue { + type Output = Expr>; + + $(fn $fn(self, rhs: Expr) -> Self::Output { + self.to_expr().$fn(rhs) + })* + } + + impl> IntCmp> for Valueless { + type Output = Valueless>; + + $(fn $fn(self, _rhs: Valueless) -> Self::Output { + Valueless { ty: UIntType::new() } + })* + } + }; +} + +cmp_op! { + CmpLt, CmpLtU, CmpLtS, cmp_lt, PartialOrd::lt; + CmpLe, CmpLeU, CmpLeS, cmp_le, PartialOrd::le; + CmpGt, CmpGtU, CmpGtS, cmp_gt, PartialOrd::gt; + CmpGe, CmpGeU, CmpGeS, cmp_ge, PartialOrd::ge; + CmpEq, CmpEqU, CmpEqS, cmp_eq, PartialEq::eq; + CmpNe, CmpNeU, CmpNeS, cmp_ne, PartialEq::ne; +} + +fixed_ary_op! { + pub struct CastInt + where ( + FromType: IntTypeTrait, + ToType: IntTypeTrait, + ) + { + pub value: Expr, + #[type(ty)] + pub ty: ToType = ty, + #[cache] + literal_bits: Result, ()> = { + value.to_literal_bits().map(|literal_bits| { + let mut bits = literal_bits.to_bitvec(); + let fill = FromType::Signed::VALUE && bits.len().checked_sub(1).map(|i| bits[i]).unwrap_or(false); + bits.resize(ty.width(), fill); + Intern::intern_owned(bits) + }) + }, + + fn simulate(&self, sim_state: &mut SimState) -> _ { + self.value.simulate(sim_state).cast(self.ty.canonical()) + } + + fn expr_enum(&self) -> _ { + struct Tag1; + impl ConstBoolDispatchTag for Tag1 { + type Type = ConstBoolDispatch< + CastInt, DynUIntType>, + CastInt, DynSIntType>, + >; + } + struct Tag2(FromSigned); + impl ConstBoolDispatchTag for Tag2 { + type Type = CastInt, DynIntType, + ) -> Self { + let v = ManuallyDrop::new(v); + let v_ptr: *const Tag::Type