diff --git a/Cargo.lock b/Cargo.lock index 0d143661..ea1f4892 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,26 @@ dependencies = [ "memchr", ] +[[package]] +name = "analytic-engine" +version = "0.1.0" +dependencies = [ + "num-bigint", + "num-traits", + "proptest", +] + +[[package]] +name = "analytic-parser" +version = "0.1.0" +dependencies = [ + "analytic-engine", + "lalrpop", + "lalrpop-util 0.21.0", + "linefeed", + "num-bigint", +] + [[package]] name = "anes" version = "0.1.6" @@ -63,6 +83,27 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -75,13 +116,28 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bit-vec", + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", ] [[package]] @@ -90,12 +146,44 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[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 = "bumpalo" version = "3.16.0" @@ -108,12 +196,6 @@ version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - [[package]] name = "cast" version = "0.3.0" @@ -178,7 +260,7 @@ checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" dependencies = [ "anstream", "anstyle", - "bitflags", + "bitflags 1.3.2", "clap_lex", "strsim 0.10.0", ] @@ -222,6 +304,21 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5bc35d2309ad4cff36f82b04bd9db6100d514990f630f6ba32a1374545dd2b2" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + [[package]] name = "criterion" version = "0.5.1" @@ -234,7 +331,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -255,7 +352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -289,6 +386,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[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 = "darling" version = "0.20.10" @@ -355,6 +462,47 @@ dependencies = [ "syn", ] +[[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 = "dirs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +dependencies = [ + "libc", + "redox_users 0.3.5", + "winapi", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + [[package]] name = "dissimilar" version = "1.0.6" @@ -406,6 +554,15 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "encoding" version = "0.2.33" @@ -470,6 +627,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" +[[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.0" @@ -522,12 +685,39 @@ dependencies = [ "typenum", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[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 = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -536,7 +726,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -549,6 +739,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.4.1" @@ -561,12 +757,31 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -608,6 +823,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -623,6 +847,56 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06093b57658c723a21da679530e061a8c25340fa5a6f98e313b542268c7e2a1f" +dependencies = [ + "ascii-canvas", + "bit-set 0.8.0", + "ena", + "itertools 0.13.0", + "lalrpop-util 0.22.0", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "108dc8f5dabad92c65a03523055577d847f5dcc00f3e7d3a68bc4d48e01d8fe1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feee752d43abd0f4807a921958ab4131f692a44d4d599733d4419c5d586176ce" +dependencies = [ + "regex-automata", + "rustversion", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -631,9 +905,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.141" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libm" @@ -641,12 +915,43 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "linefeed" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28715d08e35c6c074f9ae6b2e6a2420bac75d050c66ecd669d7d5b98e2caa036" +dependencies = [ + "dirs 1.0.5", + "mortal", + "winapi", +] + [[package]] name = "linux-raw-sys" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.21" @@ -659,6 +964,74 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mortal" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c624fa1b7aab6bd2aff6e9b18565cc0363b6d45cbcd7465c9ed5e3740ebf097" +dependencies = [ + "bitflags 2.6.0", + "libc", + "nix", + "smallstr", + "terminfo", + "unicode-normalization", + "unicode-width", + "winapi", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "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.19" @@ -681,6 +1054,92 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.7", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "plotters" version = "0.3.6" @@ -715,6 +1174,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro2" version = "1.0.92" @@ -726,20 +1191,19 @@ dependencies = [ [[package]] name = "proptest" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ - "bit-set", - "bitflags", - "byteorder", + "bit-set 0.5.3", + "bit-vec 0.6.3", + "bitflags 2.6.0", "lazy_static", "num-traits", - "quick-error 2.0.1", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.6.29", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -751,12 +1215,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" version = "1.0.37" @@ -793,7 +1251,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.8", ] [[package]] @@ -825,13 +1283,50 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom 0.1.16", + "redox_syscall 0.1.57", + "rust-argon2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.8", + "libredox", + "thiserror", ] [[package]] @@ -843,7 +1338,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.4", + "regex-syntax", ] [[package]] @@ -854,20 +1349,26 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "regex-syntax" -version = "0.8.4" +name = "rust-argon2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] [[package]] name = "rustc-hash" @@ -881,7 +1382,7 @@ version = "0.37.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -889,6 +1390,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "rusty-fork" version = "0.3.0" @@ -896,7 +1403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] @@ -916,6 +1423,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.159" @@ -947,6 +1460,50 @@ dependencies = [ "serde", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "smallstr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e922794d168678729ffc7e07182721a14219c65814e66e91b839a272fe5ae4f" +dependencies = [ + "smallvec", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", +] + [[package]] name = "strsim" version = "0.10.0" @@ -978,11 +1535,54 @@ checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", "windows-sys 0.45.0", ] +[[package]] +name = "term" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4175de05129f31b80458c6df371a15e7fc3fd367272e6bf938e5c351c7ea0" +dependencies = [ + "home", + "windows-sys 0.52.0", +] + +[[package]] +name = "terminfo" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" +dependencies = [ + "dirs 4.0.0", + "fnv", + "nom", + "phf", + "phf_codegen", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -993,6 +1593,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "typenum" version = "1.17.0" @@ -1011,12 +1626,39 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -1036,6 +1678,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1106,6 +1754,22 @@ dependencies = [ "wasm-bindgen", ] +[[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.8" @@ -1115,6 +1779,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index dc36b8c2..fe8e863f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "generated/", "doodle-formats/"] +members = [".", "generated/", "doodle-formats/", "experiments/analytic-engine/", "experiments/analytic-parser"] [package] name = "doodle" diff --git a/experiments/analytic-engine/Cargo.toml b/experiments/analytic-engine/Cargo.toml new file mode 100644 index 00000000..9c70e384 --- /dev/null +++ b/experiments/analytic-engine/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "analytic-engine" +version = "0.1.0" +edition = "2021" + +[lib] +name = "analytic_engine" +bench = false + +[dependencies] +num-bigint = "0.4" +num-traits = "0.2" +proptest = "1.5.0" diff --git a/experiments/analytic-engine/src/core.rs b/experiments/analytic-engine/src/core.rs new file mode 100644 index 00000000..4ffd9311 --- /dev/null +++ b/experiments/analytic-engine/src/core.rs @@ -0,0 +1,617 @@ +use num_bigint::BigInt; +use num_traits::{Signed, Zero}; +use std::borrow::Cow; + +pub type Number = BigInt; + +/// Standalone ground operations on two numeric arguments +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum BasicBinOp { + Add, + Sub, + Mul, + Div, + Rem, +} + +impl std::fmt::Display for BasicBinOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BasicBinOp::Add => write!(f, "+"), + BasicBinOp::Sub => write!(f, "-"), + BasicBinOp::Mul => write!(f, "*"), + BasicBinOp::Div => write!(f, "/"), + BasicBinOp::Rem => write!(f, "%"), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum BasicUnaryOp { + Negate, + AbsVal, +} + +impl std::fmt::Display for BasicUnaryOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BasicUnaryOp::Negate => write!(f, "~"), + BasicUnaryOp::AbsVal => write!(f, "abs"), + } + } +} + +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +pub enum BitWidth { + Bits8, + Bits16, + Bits32, + Bits64, +} + +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +pub struct MachineRep { + pub is_signed: bool, + pub bit_width: BitWidth, +} + +impl MachineRep { + pub const I8: Self = MachineRep { + is_signed: true, + bit_width: BitWidth::Bits8, + }; + pub const I16: Self = MachineRep { + is_signed: true, + bit_width: BitWidth::Bits16, + }; + pub const I32: Self = MachineRep { + is_signed: true, + bit_width: BitWidth::Bits32, + }; + pub const I64: Self = MachineRep { + is_signed: true, + bit_width: BitWidth::Bits64, + }; + + pub const U8: Self = MachineRep { + is_signed: false, + bit_width: BitWidth::Bits8, + }; + pub const U16: Self = MachineRep { + is_signed: false, + bit_width: BitWidth::Bits16, + }; + pub const U32: Self = MachineRep { + is_signed: false, + bit_width: BitWidth::Bits32, + }; + pub const U64: Self = MachineRep { + is_signed: false, + bit_width: BitWidth::Bits64, + }; +} + +impl From for NumRep { + fn from(value: MachineRep) -> Self { + NumRep::Concrete(value) + } +} + +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +pub enum NumRep { + Auto, + Concrete(MachineRep), +} + +impl MachineRep { + pub const fn to_static_str(self) -> &'static str { + if self.is_signed { + match self.bit_width { + BitWidth::Bits8 => "i8", + BitWidth::Bits16 => "i16", + BitWidth::Bits32 => "i32", + BitWidth::Bits64 => "i64", + } + } else { + match self.bit_width { + BitWidth::Bits8 => "u8", + BitWidth::Bits16 => "u16", + BitWidth::Bits32 => "u32", + BitWidth::Bits64 => "u64", + } + } + } +} + +impl std::fmt::Display for MachineRep { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_static_str()) + } +} + +impl NumRep { + pub const fn to_static_str(self) -> &'static str { + match self { + NumRep::Auto => "?", + NumRep::Concrete(machine) => machine.to_static_str(), + } + } +} + +impl std::fmt::Display for NumRep { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_static_str()) + } +} + +impl NumRep { + pub const I8: NumRep = NumRep::Concrete(MachineRep::I8); + pub const I16: NumRep = NumRep::Concrete(MachineRep::I16); + pub const I32: NumRep = NumRep::Concrete(MachineRep::I32); + pub const I64: NumRep = NumRep::Concrete(MachineRep::I64); + + pub const U8: NumRep = NumRep::Concrete(MachineRep::U8); + pub const U16: NumRep = NumRep::Concrete(MachineRep::U16); + pub const U32: NumRep = NumRep::Concrete(MachineRep::U32); + pub const U64: NumRep = NumRep::Concrete(MachineRep::U64); + + pub const AUTO: NumRep = NumRep::Auto; +} + +/// Representative min and max bounds for a numeric type +#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub struct Bounds { + min: Number, + max: Number, +} + +impl std::fmt::Display for Bounds { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[{},{}]", &self.min, &self.max) + } +} + +impl Bounds { + pub fn new(min: Number, max: Number) -> Self { + Self { min, max } + } + + pub fn singleton(n: Number) -> Self { + Self { + min: n.clone(), + max: n, + } + } + + /// Returns `true` if every value in `sub_range` is also within `self`. + /// + /// If `inferior` has inverted bounds, will panic. + pub fn encompasses(&self, inferior: &Self) -> bool { + assert!(inferior.min <= inferior.max); + self.min <= inferior.min && self.max >= inferior.max + } + + /// Dual to [`encompasses`]. + /// + /// Returns `true` if every value in `self` is also within `superior`. + /// + /// If `superior` has inverted bounds, will panic. + pub fn is_encompassed_by(&self, superior: &Self) -> bool { + assert!(superior.min <= superior.max); + self.min >= superior.min && self.max <= superior.max + } + + pub(crate) fn unify<'a>(&'a self, bs2: &'a Bounds) -> Cow<'a, Bounds> { + if self.is_encompassed_by(bs2) { + Cow::Borrowed(bs2) + } else if self.encompasses(bs2) { + Cow::Borrowed(self) + } else { + Cow::Owned(Bounds { + min: Ord::min(&self.min, &bs2.min).clone(), + max: Ord::max(&self.max, &bs2.max).clone(), + }) + } + } +} + +macro_rules! bounds_of { + ( $t:ty ) => { + (Number::from(<$t>::MIN), Number::from(<$t>::MAX)) + }; +} + +impl MachineRep { + pub(crate) fn as_bounds(self) -> Bounds { + let (min, max) = match self { + Self::U8 => bounds_of!(u8), + Self::U16 => bounds_of!(u16), + Self::U32 => bounds_of!(u32), + Self::U64 => bounds_of!(u64), + Self::I8 => bounds_of!(i8), + Self::I16 => bounds_of!(i16), + Self::I32 => bounds_of!(i32), + Self::I64 => bounds_of!(i64), + }; + Bounds { min, max } + } + + pub(crate) const fn is_signed(self) -> bool { + self.is_signed + } + + pub(crate) fn compare_width(self, other: Self) -> std::cmp::Ordering { + self.bit_width.cmp(&other.bit_width) + } + + pub(crate) fn encompasses(self, other: Self) -> bool { + self.as_bounds().encompasses(&other.as_bounds()) + } +} + +impl NumRep { + pub(crate) fn as_bounds(self) -> Option { + match self { + NumRep::Auto => return None, + NumRep::Concrete(mr) => Some(mr.as_bounds()), + } + } + + pub(crate) const fn is_auto(self) -> bool { + matches!(self, NumRep::Auto) + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct TypedConst(pub BigInt, pub NumRep); + +impl std::fmt::Display for TypedConst { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let n = &self.0; + let r = &self.1; + write!(f, "{n}{r}") + } +} + +impl TypedConst { + /// Returns `true` if the stored `NumRep` is abstract (either auto or ambiguous). + pub fn is_abstract(self) -> bool { + self.1.is_auto() + } + + /// Returns `true` if `self` is representable, which is true if either: + /// - The `NumRep` is `Abstract` + /// - The `NumRep` is concrete and `n` is in the bounds of the `NumRep` + pub fn is_representable(&self) -> bool { + let TypedConst(ref n, rep) = self; + if let Some(bounds) = rep.as_bounds() { + n >= &bounds.min && n <= &bounds.max + } else { + debug_assert!(rep.is_auto()); + true + } + } + + pub fn as_raw_value(&self) -> &BigInt { + &self.0 + } + + /// Type-agnostic equality on a pure mathematical level. + /// + /// Does not check for representablity of either value, nor even whether either representative is some flavor of `Abstract`. + pub fn eq_val(&self, other: &TypedConst) -> bool { + &self.0 == &other.0 + } + + /// Numeric equality test on `self`, that the value it holds is equal to `other` regardless of type. + /// + /// Saves the construction of a new TypedConst compared to [`eq_val`] if the query is made starting with a BigInt in mind. + pub fn eq_num(&self, other: &BigInt) -> bool { + &self.0 == other + } + + /// Returns the NumRep of a `TypedConst`. + pub fn get_rep(&self) -> NumRep { + self.1 + } +} + +#[derive(Clone, PartialEq, Debug)] +pub enum Value { + Const(TypedConst), + // Opt(Option>), +} + +impl Value { + /// Returns `true` if `self` is representable: + /// - If `self` is a constant value, it must itself be representable + /// - If `self` is Some(x), `x` must be representable + /// + /// `None` is always representable. + pub fn is_representable(&self) -> bool { + match self { + Value::Const(c) => c.is_representable(), + // Value::Opt(value) => value.as_deref().map_or(true, Value::is_representable), + } + } + + /// Extracts a reference to the `TypedConst` held within a Value, irrespective of its numeric representative. + pub fn as_const(&self) -> Option<&TypedConst> { + match self { + Value::Const(c) => Some(c), + // Value::Opt(value) => value.as_deref().and_then(Value::as_const), + } + } +} + +impl std::fmt::Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Const(num) => write!(f, "{num}"), + // Value::Opt(None) => write!(f, "None"), + // Value::Opt(Some(x)) => write!(f, "Some({})", x), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct BinOp { + op: BasicBinOp, + // If None: op(T, T | auto) -> T, op(T0, T1) { T0 != T1 } -> ambiguous; otherwise, forces rep for `Some(rep)`` + out_rep: Option, +} + +impl std::fmt::Display for BinOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.out_rep { + None => write!(f, "{}", self.op), + Some(rep) => write!(f, "{}{}", self.op, rep), + } + } +} + +impl BinOp { + pub const fn new(op: BasicBinOp, out_rep: Option) -> Self { + Self { op, out_rep } + } + + pub fn cast_rep(&self) -> Option { + self.out_rep + } + + pub fn is_cast_and(&self, predicate: impl Fn(MachineRep) -> bool) -> bool { + self.out_rep.is_some_and(predicate) + } + + pub(crate) fn get_op(&self) -> BasicBinOp { + self.op + } +} + +#[derive(Clone, Copy, Debug)] +pub struct UnaryOp { + op: BasicUnaryOp, + // If None, will pick the same type as the input (even if this produces a temporary unrepresentable) + out_rep: Option, +} + +impl std::fmt::Display for UnaryOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.out_rep { + None => write!(f, "{}", self.op), + Some(rep) => write!(f, "{}{}", self.op, rep), + } + } +} + +impl UnaryOp { + pub const fn new(op: BasicUnaryOp, out_rep: Option) -> Self { + Self { op, out_rep } + } + + pub fn cast_rep(&self) -> Option { + self.out_rep + } + + pub fn is_cast_and(&self, predicate: fn(MachineRep) -> bool) -> bool { + self.out_rep.is_some_and(predicate) + } + + pub(crate) fn get_op(&self) -> BasicUnaryOp { + self.op + } +} + +#[derive(Clone)] +pub enum Expr { + Const(TypedConst), + BinOp(BinOp, Box, Box), + UnaryOp(UnaryOp, Box), + Cast(MachineRep, Box), +} + +impl std::fmt::Debug for Expr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Const(arg0) => write!(f, "{}", arg0), + Self::BinOp(op, lhs, rhs) => write!(f, "({:?} {} {:?})", lhs, op, rhs), + Self::UnaryOp(op, inner) => write!(f, "{}({:?})", op, inner), + Self::Cast(rep, inner) => write!(f, "Cast({:?}, {:?})", inner, rep), + } + } +} + +#[derive(Debug)] +pub enum EvalError { + DivideByZero, + RemainderNonPositive, + Ambiguous(NumRep, NumRep), +} + +impl std::fmt::Display for EvalError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EvalError::DivideByZero => write!(f, "attempted division by zero"), + EvalError::RemainderNonPositive => write!(f, "remainder rhs must be positive"), + EvalError::Ambiguous(rep0, rep1) => { + write!(f, "operation over {rep0} and {rep1} must have an explicit output representation to be evaluated") + } + } + } +} + +impl std::error::Error for EvalError {} + +impl Expr { + pub fn eval(&self) -> Result { + match self { + Expr::Const(typed_const) => Ok(Value::Const(typed_const.clone())), + Expr::BinOp(bin_op, lhs, rhs) => { + let lhs = lhs.eval()?; + let rhs = rhs.eval()?; + let BinOp { op, out_rep } = *bin_op; + let (raw, rep0, rep1) = match (op, lhs, rhs) { + (BasicBinOp::Add, Value::Const(lhs), Value::Const(rhs)) => { + (lhs.0 + rhs.0, lhs.1, rhs.1) + } + (BasicBinOp::Sub, Value::Const(lhs), Value::Const(rhs)) => { + (lhs.0 - rhs.0, lhs.1, rhs.1) + } + (BasicBinOp::Mul, Value::Const(lhs), Value::Const(rhs)) => { + (lhs.0 * rhs.0, lhs.1, rhs.1) + } + (BasicBinOp::Div, Value::Const(lhs), Value::Const(rhs)) => { + if rhs.0.is_zero() { + return Err(EvalError::DivideByZero); + } + (lhs.0 / rhs.0, lhs.1, rhs.1) + } + (BasicBinOp::Rem, Value::Const(lhs), Value::Const(rhs)) => { + if rhs.0.is_positive() { + (lhs.0 % rhs.0, lhs.1, rhs.1) + } else { + return Err(EvalError::RemainderNonPositive); + } + } // (_, Value::Opt(..), _) | (_, _, Value::Opt(..)) => { + // return Err(EvalError::ArithOrCastOption) + // } + }; + let rep_out = match out_rep { + Some(rep) => NumRep::Concrete(rep), + None => { + if rep0 == rep1 || rep1.is_auto() { + rep0 + } else if rep0.is_auto() { + rep1 + } else { + return Err(EvalError::Ambiguous(rep0, rep1)); + } + } + }; + Ok(Value::Const(TypedConst(raw, rep_out))) + } + Expr::UnaryOp(unary_op, expr) => { + let expr = expr.eval()?; + match (unary_op.op, expr) { + (BasicUnaryOp::Negate, Value::Const(TypedConst(n, rep))) => { + let rep_out = match unary_op.out_rep { + Some(rep) => NumRep::Concrete(rep), + None => rep, + }; + Ok(Value::Const(TypedConst(-n, rep_out))) + } + (BasicUnaryOp::AbsVal, Value::Const(TypedConst(n, rep))) => { + let rep_out = match unary_op.out_rep { + Some(rep) => NumRep::Concrete(rep), + None => rep, + }; + Ok(Value::Const(TypedConst(n.abs(), rep_out))) + } + } + } + Expr::Cast(mach_rep, expr) => { + let val = expr.eval()?; + match val { + Value::Const(TypedConst(num, _rep)) => { + Ok(Value::Const(TypedConst(num, NumRep::Concrete(*mach_rep)))) + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::core::*; + use num_traits::One; + use proptest::prelude::*; + + fn abstract_strategy() -> BoxedStrategy { + prop_oneof![Just(NumRep::AUTO)].boxed() + } + + fn concrete_strategy() -> BoxedStrategy { + prop_oneof![ + Just(NumRep::U8), + Just(NumRep::U16), + Just(NumRep::U32), + Just(NumRep::U64), + Just(NumRep::I8), + Just(NumRep::I16), + Just(NumRep::I32), + Just(NumRep::I64), + ] + .boxed() + } + + fn numrep_strategy() -> BoxedStrategy { + prop_oneof![abstract_strategy(), concrete_strategy(),].boxed() + } + + #[test] + fn one_plus_one_is_two() -> Result<(), EvalError> { + let one = TypedConst(BigInt::one(), NumRep::AUTO); + let should_be_two = Expr::BinOp( + BinOp { + op: BasicBinOp::Add, + out_rep: None, + }, + Box::new(Expr::Const(one.clone())), + Box::new(Expr::Const(one)), + ); + assert!(should_be_two + .eval()? + .as_const() + .unwrap() + .eq_num(&BigInt::from(2))); + Ok(()) + } + + proptest! { + #[test] + fn cast_works(orig in numrep_strategy(), tgt in numrep_strategy()) { + let one = TypedConst(BigInt::one(), orig); + let casted_one = Expr::Cast(tgt, Box::new(Expr::Const(one))); + let val = casted_one.eval().unwrap(); + let rep = val.as_const().unwrap().get_rep(); + prop_assert_eq!(rep, tgt); + } + + #[test] + fn auto_is_eagerly_erased(rep in numrep_strategy()) { + let one = TypedConst(BigInt::one(), NumRep::AUTO); + let rep_one = TypedConst(BigInt::one(), rep); + let two_should_be_rep = Expr::BinOp( + BinOp { + op: BasicBinOp::Add, + out_rep: None, + }, + Box::new(Expr::Const(one)), + Box::new(Expr::Const(rep_one)), + ); + let actual = two_should_be_rep.eval().unwrap().as_const().unwrap().get_rep(); + prop_assert_eq!(actual, rep); + } + } +} diff --git a/experiments/analytic-engine/src/elaborator.rs b/experiments/analytic-engine/src/elaborator.rs new file mode 100644 index 00000000..075b6e51 --- /dev/null +++ b/experiments/analytic-engine/src/elaborator.rs @@ -0,0 +1,1143 @@ +use crate::core::{BinOp, Expr, MachineRep, NumRep, TypedConst, UnaryOp}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum PrimInt { + U8, + U16, + U32, + U64, + I8, + I16, + I32, + I64, +} + +impl PrimInt { + pub const fn to_static_str(self) -> &'static str { + match self { + PrimInt::U8 => "u8", + PrimInt::U16 => "u16", + PrimInt::U32 => "u32", + PrimInt::U64 => "u64", + PrimInt::I8 => "i8", + PrimInt::I16 => "i16", + PrimInt::I32 => "i32", + PrimInt::I64 => "i64", + } + } +} + +pub(crate) const PRIM_INTS: [PrimInt; 8] = [ + PrimInt::U8, + PrimInt::U16, + PrimInt::U32, + PrimInt::U64, + PrimInt::I8, + PrimInt::I16, + PrimInt::I32, + PrimInt::I64, +]; + +#[derive(Debug)] +pub struct TryFromAutoError; + +impl std::fmt::Display for TryFromAutoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "cannot convert `NumRep::AUTO` to `PrimInt`") + } +} + +impl std::error::Error for TryFromAutoError {} + +impl TryFrom for PrimInt { + type Error = TryFromAutoError; + + fn try_from(value: NumRep) -> Result { + match value { + NumRep::Auto => Err(TryFromAutoError), + NumRep::Concrete(mr) => Ok(mr.into()), + } + } +} + +impl From for PrimInt { + fn from(value: MachineRep) -> Self { + match value { + MachineRep::U8 => PrimInt::U8, + MachineRep::U16 => PrimInt::U16, + MachineRep::U32 => PrimInt::U32, + MachineRep::U64 => PrimInt::U64, + MachineRep::I8 => PrimInt::I8, + MachineRep::I16 => PrimInt::I16, + MachineRep::I32 => PrimInt::I32, + MachineRep::I64 => PrimInt::I64, + } + } +} + +impl From for MachineRep { + fn from(value: PrimInt) -> Self { + match value { + PrimInt::U8 => MachineRep::U8, + PrimInt::U16 => MachineRep::U16, + PrimInt::U32 => MachineRep::U32, + PrimInt::U64 => MachineRep::U64, + PrimInt::I8 => MachineRep::I8, + PrimInt::I16 => MachineRep::I16, + PrimInt::I32 => MachineRep::I32, + PrimInt::I64 => MachineRep::I64, + } + } +} + +impl From for NumRep { + fn from(value: IntType) -> Self { + match value { + IntType::Prim(prim) => NumRep::from(prim), + } + } +} + +impl From for NumRep { + fn from(value: PrimInt) -> Self { + match value { + PrimInt::U8 => NumRep::U8, + PrimInt::U16 => NumRep::U16, + PrimInt::U32 => NumRep::U32, + PrimInt::U64 => NumRep::U64, + PrimInt::I8 => NumRep::I8, + PrimInt::I16 => NumRep::I16, + PrimInt::I32 => NumRep::I32, + PrimInt::I64 => NumRep::I64, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(crate) enum IntType { + Prim(PrimInt), +} + +impl IntType { + pub fn to_prim(self) -> PrimInt { + let IntType::Prim(ret) = self; + ret + } + + pub const fn to_static_str(self) -> &'static str { + match self { + Self::Prim(p) => p.to_static_str(), + } + } +} + +impl std::fmt::Display for IntType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_static_str()) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum TypedExpr { + ElabConst(TypeRep, TypedConst), + ElabBinOp( + TypeRep, + TypedBinOp, + Box>, + Box>, + ), + ElabUnaryOp(TypeRep, TypedUnaryOp, Box>), + ElabCast(TypeRep, TypedCast, Box>), +} + +impl TypedExpr { + pub fn get_type(&self) -> &T { + match self { + TypedExpr::ElabConst(t, _) => t, + TypedExpr::ElabBinOp(t, _, _, _) => t, + TypedExpr::ElabUnaryOp(t, _, _) => t, + TypedExpr::ElabCast(t, _, _) => t, + } + } +} + +pub(crate) type Sig1 = (T, T); +pub(crate) type Sig2 = ((T, T), T); + +#[derive(Clone, Debug)] +pub(crate) struct TypedBinOp { + pub(crate) sig: Sig2, + pub(crate) inner: BinOp, +} + +#[derive(Clone, Debug)] +pub(crate) struct TypedUnaryOp { + pub(crate) sig: Sig1, + pub(crate) inner: UnaryOp, +} + +#[derive(Clone, Copy, Debug)] +pub(crate) struct TypedCast { + pub(crate) sig: Sig1, + pub(crate) _rep: NumRep, +} +pub(crate) mod inference { + use std::collections::HashSet; + + use crate::core::{Bounds, Expr, MachineRep, NumRep}; + + use super::{IntType, PrimInt}; + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[repr(transparent)] + pub struct UVar(usize); + + impl std::fmt::Display for UVar { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "?{}", self.0) + } + } + + impl UVar { + pub fn new(ix: usize) -> Self { + Self(ix) + } + + // pub fn to_usize(self) -> usize { + // self.0 + // } + } + + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] + pub enum UType { + Var(UVar), + Int(IntType), + } + + impl From for UType { + fn from(value: IntType) -> Self { + UType::Int(value) + } + } + + impl From for UType { + fn from(value: UVar) -> Self { + UType::Var(value) + } + } + + #[derive(Clone, Debug, PartialEq, Eq)] + pub(crate) enum VType { + Int(IntType), + Within(Bounds), + Abstract(UType), + } + + #[derive(Clone, Debug, Default)] + enum Alias { + #[default] + Ground, + BackRef(usize), + Canonical(HashSet), + } + + impl Alias { + pub const fn new() -> Alias { + Alias::Ground + } + + pub fn is_canonical_nonempty(&self) -> bool { + match self { + Alias::Canonical(x) => !x.is_empty(), + _ => false, + } + } + + pub fn add_forward_ref(&mut self, tgt: usize) { + match self { + Alias::Ground => { + let _ = std::mem::replace(self, Alias::Canonical(HashSet::from([tgt]))); + } + Alias::BackRef(_ix) => panic!("cannot add forward-ref to Alias::BackRef"), + Alias::Canonical(fwds) => { + fwds.insert(tgt); + } + } + } + + fn set_backref(&mut self, tgt: usize) -> Alias { + std::mem::replace(self, Alias::BackRef(tgt)) + } + + fn iter_fwd_refs<'a>(&'a self) -> Box + 'a> { + match self { + Alias::Ground | Alias::BackRef(_) => Box::new(std::iter::empty()), + Alias::Canonical(fwds) => Box::new(fwds.iter().copied()), + } + } + + fn contains_fwd_ref(&self, tgt: usize) -> bool { + match self { + Alias::Ground | Alias::BackRef(_) => false, + Alias::Canonical(fwds) => fwds.contains(&tgt), + } + } + } + + #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] + pub enum Constraints { + #[default] + Indefinite, + Invariant(Constraint), + } + + impl Constraints { + pub fn new() -> Constraints { + Constraints::Indefinite + } + } + + #[derive(Clone, Debug, PartialEq, Eq, Hash)] + pub enum Constraint { + Equiv(UType), + // Must be able to represent all values within the specified range + Encompasses(crate::core::Bounds), + } + + impl std::fmt::Display for Constraint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Constraint::Equiv(utype) => write!(f, "= {utype:?}"), + Constraint::Encompasses(bounds) => write!(f, "∈ {}", bounds), + } + } + } + + impl From for Constraint { + fn from(value: UType) -> Self { + Constraint::Equiv(value) + } + } + + impl Constraint { + /// Speculatively checks if this constraint is definitely satisfiable (as-is) by a given type-assignment. + /// + /// If this is not statically deterministic, returns `None`. + /// Returns `Some(true)` if the constraint is satisfiable by the assignment, and `Some(false)` otherwise. + pub(crate) fn is_satisfied_by(&self, candidate: IntType) -> Option { + match self { + Constraint::Equiv(utype) => match utype { + UType::Var(_) => None, + UType::Int(int_type) => Some(int_type == &candidate), + }, + Constraint::Encompasses(bounds) => { + let IntType::Prim(candidate) = candidate; + Some( + bounds.is_encompassed_by( + &>::into(candidate).as_bounds() + ), + ) + } + } + } + + // NOTE - should only be called on Encompasses + pub(crate) fn get_unique_solution(&self) -> InferenceResult { + // REVIEW - there are smarter ways of calculating this + let mut solutions = Vec::with_capacity(8); + for prim_int in super::PRIM_INTS.iter() { + match self.is_satisfied_by(IntType::Prim(*prim_int)) { + Some(true) => { + solutions.push(*prim_int); + } + Some(false) => (), + None => panic!("unexpected call to get_unique_solution on `{self}` (either trivial or insoluble)"), + } + } + match solutions.as_slice() { + [] => Err(InferenceError::NoSolution), + [uniq] => Ok(IntType::Prim(*uniq)), + _ => Err(InferenceError::MultipleSolutions), + } + } + } + + #[derive(Debug)] + pub struct InferenceEngine { + constraints: Vec, + aliases: Vec, + } + + #[derive(Debug)] + pub enum InferenceError { + // Unrepresentable(TypedConst, IntType), + BadUnification(Constraint, Constraint), + Ambiguous, + NoSolution, + MultipleSolutions, + Eval(crate::core::EvalError), + } + + impl std::fmt::Display for InferenceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + // InferenceError::Unrepresentable(c, int_type) => write!(f, "inference requires that `{}` be assigned type `{}`, which cannot represent it", c, int_type), + InferenceError::BadUnification(cx1, cx2) => write!(f, "constraints `{}` and `{}` cannot be unified", cx1, cx2), + InferenceError::Ambiguous => write!(f, "mixed-type binary operation must have out_rep on operation to avoid ambiguity"), + InferenceError::Eval(e) => write!(f, "inference abandoned due to evaluation error: {}", e), + InferenceError::NoSolution => write!(f, "no valid assignment of PrimInt types produce a fully representable tree"), + InferenceError::MultipleSolutions => write!(f, "multiple assignments of PrimInt produce a fully representable tree, in absence of tie-breaking mechanism"), + } + } + } + + impl std::error::Error for InferenceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + InferenceError::Eval(e) => Some(e), + _ => None, + } + } + } + + pub type InferenceResult = Result; + + impl InferenceEngine { + pub fn new() -> Self { + Self { + constraints: Vec::new(), + aliases: Vec::new(), + } + } + + pub fn init_var_simple(&mut self, typ: UType) -> InferenceResult<(UVar, UType)> { + let newvar = self.get_new_uvar(); + let constr = Constraint::Equiv(typ); + self.unify_var_constraint(newvar, constr)?; + Ok((newvar, typ)) + } + + fn get_new_uvar(&mut self) -> UVar { + let ret = UVar(self.constraints.len()); + self.constraints.push(Constraints::new()); + self.aliases.push(Alias::new()); + ret + } + } + + impl InferenceEngine { + #[cfg_attr(not(test), allow(dead_code))] + pub fn check_uvar_sanity(&self) { + assert_eq!(self.constraints.len(), self.aliases.len()); + } + } + + impl InferenceEngine { + fn get_canonical_uvar(&self, v: UVar) -> UVar { + match self.aliases[v.0] { + Alias::Canonical(_) | Alias::Ground => v, + Alias::BackRef(ix) => UVar(ix), + } + } + + fn unify_var_constraint( + &mut self, + uvar: UVar, + constraint: Constraint, + ) -> InferenceResult { + let can_ix = self.get_canonical_uvar(uvar).0; + + match &self.constraints[can_ix] { + Constraints::Indefinite => { + let ret = constraint.clone(); + self.constraints[can_ix] = Constraints::Invariant(constraint); + Ok(ret) + } + Constraints::Invariant(prior) => { + let c1 = prior.clone(); + if c1 == constraint { + return Ok(c1); + } + let ret = self.unify_constraint_pair(c1, constraint)?; + self.constraints[can_ix] = Constraints::Invariant(ret.clone()); + Ok(ret) + } + } + } + + unsafe fn repoint(&mut self, lo: usize, hi: usize) { + self.aliases[hi].set_backref(lo); + self.aliases[lo].add_forward_ref(hi); + } + + unsafe fn transfer_constraints( + &mut self, + a1: usize, + a2: usize, + ) -> InferenceResult<&Constraints> { + if a1 == a2 { + return Ok(&self.constraints[a1]); + } + + match (&self.constraints[a1], &self.constraints[a2]) { + (Constraints::Indefinite, Constraints::Indefinite) => Ok(&Constraints::Indefinite), + (Constraints::Indefinite, _) => Ok(self.replace_constraints_from_index(a1, a2)), + (_, Constraints::Indefinite) => Ok(self.replace_constraints_from_index(a2, a1)), + (Constraints::Invariant(c1), Constraints::Invariant(c2)) => { + let c0 = self.unify_constraint_pair(c1.clone(), c2.clone())?; + let _ = + self.replace_constraints_with_value(a1, Constraints::Invariant(c0.clone())); + let _ = self.replace_constraints_with_value(a2, Constraints::Invariant(c0)); + Ok(&self.constraints[a1]) + } + } + } + + #[must_use] + fn replace_constraints_from_index(&mut self, tgt_ix: usize, src_ix: usize) -> &Constraints { + assert_ne!(tgt_ix, src_ix); + let val = self.constraints[src_ix].clone(); + self.replace_constraints_with_value(tgt_ix, val) + } + + #[must_use] + fn replace_constraints_with_value(&mut self, ix: usize, val: Constraints) -> &Constraints { + self.constraints[ix] = val; + &self.constraints[ix] + } + + fn unify_var_pair(&mut self, v1: UVar, v2: UVar) -> InferenceResult<&Constraints> { + if v1 == v2 { + return Ok(&self.constraints[v1.0]); + } + + // short-circuit if already equated + match (&self.aliases[v1.0], &self.aliases[v2.0]) { + (Alias::Ground, Alias::Ground) => { + if v1 < v2 { + unsafe { + self.repoint(v1.0, v2.0); + self.transfer_constraints(v1.0, v2.0) + } + } else { + unsafe { + self.repoint(v2.0, v1.0); + self.transfer_constraints(v2.0, v1.0) + } + } + } + (Alias::Ground, &Alias::BackRef(can_ix)) if v1.0 > can_ix => unsafe { + self.repoint(can_ix, v1.0); + self.transfer_constraints(can_ix, v1.0) + }, + (Alias::Ground, &Alias::BackRef(can_ix)) if v1.0 < can_ix => { + debug_assert!( + self.aliases[can_ix].is_canonical_nonempty(), + "half-alias ?{can_ix}-|<-{v1}" + ); + debug_assert!( + !self.aliases[can_ix].contains_fwd_ref(v2.0), + "retrograde half-aliased 'forward' ref ?{can_ix}->|-{v2}" + ); + unsafe { self.recanonicalize(v1.0, can_ix) } + } + (Alias::Ground, &Alias::BackRef(_can_ix)) => { + unreachable!("unexpected half-alias {v1}-|<-{v2}"); + } + (&Alias::BackRef(can_ix), Alias::Ground) if v2.0 > can_ix => unsafe { + self.repoint(can_ix, v2.0); + self.transfer_constraints(can_ix, v2.0) + }, + (&Alias::BackRef(can_ix), Alias::Ground) if v2.0 < can_ix => { + debug_assert!( + self.aliases[can_ix].is_canonical_nonempty(), + "half-alias ?{can_ix}-|<-{v1}" + ); + debug_assert!( + !self.aliases[can_ix].contains_fwd_ref(v2.0), + "retrograde half-aliased 'forward' ref ?{can_ix}->|-{v2}" + ); + unsafe { self.recanonicalize(v2.0, can_ix) } + } + (&Alias::BackRef(_can_ix), Alias::Ground) => { + unreachable!("unexpected half-alias {v2}-|<-{v1}"); + } + (Alias::Ground, Alias::Canonical(_)) => { + if v1.0 < v2.0 { + debug_assert!( + !self.aliases[v2.0].contains_fwd_ref(v1.0), + "retrograde half-aliased 'forward' ref {v2}->|-{v1}" + ); + unsafe { self.recanonicalize(v1.0, v2.0) } + } else { + unsafe { + self.repoint(v2.0, v1.0); + self.transfer_constraints(v2.0, v1.0) + } + } + } + (Alias::Canonical(_), Alias::Ground) => { + if v2.0 < v1.0 { + debug_assert!( + !self.aliases[v1.0].contains_fwd_ref(v2.0), + "retrograde half-aliased 'forward' ref {v1}->|-{v2}" + ); + unsafe { self.recanonicalize(v2.0, v1.0) } + } else { + unsafe { + self.repoint(v1.0, v2.0); + self.transfer_constraints(v1.0, v2.0) + } + } + } + (&Alias::BackRef(ix1), &Alias::BackRef(ix2)) if ix1 < ix2 => unsafe { + self.recanonicalize(ix1, ix2) + }, + (&Alias::BackRef(ix1), &Alias::BackRef(ix2)) if ix2 < ix1 => unsafe { + self.recanonicalize(ix2, ix1) + }, + (&Alias::BackRef(ix), &Alias::BackRef(_ix)) => { + // the two are equal so nothing needs to be changed; we will check both are forward-aliased, however + let common = &self.aliases[ix]; + debug_assert!( + common.contains_fwd_ref(v1.0), + "unexpected half-alias ?{ix}<-{v1}" + ); + debug_assert!( + common.contains_fwd_ref(v2.0), + "unexpected half-alias ?{ix}<-{v2}" + ); + Ok(&self.constraints[ix]) + } + (a1 @ Alias::BackRef(tgt), a2 @ Alias::Canonical(fwds)) => { + let left = fwds.contains(&v1.0); + let right = *tgt == v2.0; + + match (left, right) { + (true, true) => { + return Ok(&self.constraints[v2.0]); + } + (true, false) | (false, true) => { + unreachable!( + "mismatched back- and forward-references for {v1} ({a1:?}) and {v2} ({a2:?})" + ) + } + (false, false) => (), + } + + let ix1 = *tgt; + let ix2 = v2.0; + + // check not the actual indices, but the canonical indices for tie-breaking + if ix1 < ix2 { + unsafe { self.recanonicalize(ix1, ix2) } + } else { + unsafe { self.recanonicalize(ix2, ix1) } + } + } + (a1 @ Alias::Canonical(fwds), a2 @ Alias::BackRef(tgt)) => { + let left = fwds.contains(&v2.0); + let right = *tgt == v1.0; + + match (left, right) { + (true, true) => { + return Ok(&self.constraints[v1.0]); + } + (true, false) | (false, true) => { + unreachable!( + "mismatched forward- and back-references for {v1} ({a1:?}) and {v2} ({a2:?})" + ) + } + (false, false) => (), + } + + let ix1 = v1.0; + let ix2 = *tgt; + + // check not the actual indices, but the canonical indices for tie-breaking + if ix1 < ix2 { + unsafe { self.recanonicalize(ix1, ix2) } + } else { + unsafe { self.recanonicalize(ix2, ix1) } + } + } + (Alias::Canonical(_), Alias::Canonical(_)) => { + if v1 < v2 { + unsafe { self.recanonicalize(v1.0, v2.0) } + } else { + unsafe { self.recanonicalize(v2.0, v1.0) } + } + } + } + } + + unsafe fn recanonicalize(&mut self, a1: usize, a2: usize) -> InferenceResult<&Constraints> { + let tmp = self.aliases[a2].set_backref(a1); + let iter = tmp.iter_fwd_refs(); + for a in iter { + assert!( + !self.aliases[a1].contains_fwd_ref(a), + "forward ref of ?{a2} is also a forward_ref of ?{a1}, somehow" + ); + self.repoint(a1, a); + } + self.aliases[a1].add_forward_ref(a2); + self.transfer_constraints(a1, a2) + } + + fn unify_utype(&mut self, left: UType, right: UType) -> InferenceResult { + match (left, right) { + (UType::Var(v1), UType::Var(v2)) => { + self.unify_var_pair(v1, v2)?; + Ok(UType::Var(Ord::min(v1, v2))) + } + (UType::Var(v), _) => { + let constraint = Constraint::Equiv(right); + let after = self.unify_var_constraint(v, constraint)?; + match after { + Constraint::Equiv(t) => Ok(t), + Constraint::Encompasses(_) => { + unreachable!("equiv should erase encompasses") + } + } + } + (_, UType::Var(v)) => { + let constraint = Constraint::Equiv(left); + let after = self.unify_var_constraint(v, constraint)?; + match after { + Constraint::Equiv(t) => Ok(t), + Constraint::Encompasses(_) => { + unreachable!("equiv should erase encompasses") + } + } + } + (UType::Int(t0), UType::Int(t1)) => { + if t0 != t1 { + return Err(InferenceError::BadUnification( + Constraint::Equiv(left), + Constraint::Equiv(right), + )); + } + Ok(left) + } + } + } + + fn unify_utype_bounds( + &mut self, + utype: UType, + bounds: &Bounds, + ) -> InferenceResult { + match utype { + UType::Var(uvar) => { + self.unify_var_constraint(uvar, Constraint::Encompasses(bounds.clone())) + } + UType::Int(int_type) => { + let IntType::Prim(candidate) = int_type; + let soluble = bounds.is_encompassed_by( + &>::into(candidate) + .as_bounds(), + ); + if soluble { + Ok(Constraint::Equiv(utype)) + } else { + Err(InferenceError::BadUnification( + Constraint::Equiv(utype), + Constraint::Encompasses(bounds.clone()), + )) + } + } + } + } + + fn unify_var_utype(&mut self, uvar: UVar, utype: UType) -> InferenceResult<()> { + let _ = self.unify_var_constraint(uvar, Constraint::Equiv(utype))?; + Ok(()) + } + + fn unify_var_rep(&mut self, uvar: UVar, rep: NumRep) -> InferenceResult<()> { + if rep.is_auto() { + return Ok(()); + } + let t = UType::Int(IntType::Prim(PrimInt::try_from(rep).unwrap())); + self.unify_var_utype(uvar, t) + } + + fn unify_constraint_pair( + &mut self, + c1: Constraint, + c2: Constraint, + ) -> InferenceResult { + match (c1, c2) { + (Constraint::Equiv(t1), Constraint::Equiv(t2)) => { + if t1 == t2 { + Ok(Constraint::Equiv(t1)) + } else { + let t0 = self.unify_utype(t1, t2)?; + Ok(Constraint::Equiv(t0)) + } + } + (Constraint::Equiv(utype), Constraint::Encompasses(bounds)) + | (Constraint::Encompasses(bounds), Constraint::Equiv(utype)) => { + Ok(self.unify_utype_bounds(utype, &bounds)?) + } + (Constraint::Encompasses(bs1), Constraint::Encompasses(bs2)) => { + let bs0 = bs1.unify(&bs2).into_owned(); + Ok(Constraint::Encompasses(bs0)) + } + } + } + } + + impl InferenceEngine { + pub(crate) fn infer_var_expr(&mut self, e: &Expr) -> InferenceResult<(UVar, NumRep)> { + let (top_var, top_rep) = match e { + Expr::Const(typed_const) => { + let rep = typed_const.get_rep(); + let var = match rep { + NumRep::AUTO => { + let this_var = self.get_new_uvar(); + self.unify_var_constraint( + this_var, + Constraint::Encompasses(Bounds::singleton( + typed_const.as_raw_value().clone(), + )), + )?; + this_var + } + NumRep::U8 => { + self.init_var_simple(UType::Int(IntType::Prim(PrimInt::U8)))? + .0 + } + NumRep::U16 => { + self.init_var_simple(UType::Int(IntType::Prim(PrimInt::U16)))? + .0 + } + NumRep::U32 => { + self.init_var_simple(UType::Int(IntType::Prim(PrimInt::U32)))? + .0 + } + NumRep::U64 => { + self.init_var_simple(UType::Int(IntType::Prim(PrimInt::U64)))? + .0 + } + NumRep::I8 => { + self.init_var_simple(UType::Int(IntType::Prim(PrimInt::I8)))? + .0 + } + NumRep::I16 => { + self.init_var_simple(UType::Int(IntType::Prim(PrimInt::I16)))? + .0 + } + NumRep::I32 => { + self.init_var_simple(UType::Int(IntType::Prim(PrimInt::I32)))? + .0 + } + NumRep::I64 => { + self.init_var_simple(UType::Int(IntType::Prim(PrimInt::I64)))? + .0 + } + }; + (var, rep) + } + Expr::BinOp(bin_op, lhs, rhs) => { + let this_var = self.get_new_uvar(); + let (l_var, l_rep) = self.infer_var_expr(&lhs)?; + let (r_var, r_rep) = self.infer_var_expr(&rhs)?; + let cast_rep = bin_op.cast_rep(); + let this_rep = match (l_rep, r_rep) { + (NumRep::AUTO, NumRep::AUTO) => { + self.unify_var_pair(this_var, l_var)?; + self.unify_var_pair(this_var, r_var)?; + if let Some(rep) = cast_rep { + self.unify_var_rep(this_var, NumRep::Concrete(rep))?; + NumRep::Concrete(rep) + } else { + { + // REVIEW - do we need to go this far? + match e.eval() { + Ok(v) => match v.as_const() { + Some(c) => { + // NOTE - if there is a const-evaluable result for the computation, use it to refine our constraints on which types satisfy the aliased Auto + let bounds = + Bounds::singleton(c.as_raw_value().clone()); + self.unify_var_constraint( + this_var, + Constraint::Encompasses(bounds), + )?; + } + None => { + // FIXME - this isn't a hard error necessarily, but our model isn't complex enough for non-TypedConst values to emerge + unimplemented!("Value::AsConst returned None (unexpectedly) when called from InferenceEngine::infer_var_expr"); + } + }, + Err(e) => { + // NOTE - If the computation will fail regardless, there is no need to infer the type-information of the AST + // REVIEW - make sure that we are confident in EvalErrors being sound reasons to fail type-inference, both now and going forward + return Err(InferenceError::Eval(e)); + } + } + } + NumRep::AUTO + } + } + (rep0, rep1) if rep0 == rep1 => { + if let Some(rep) = cast_rep { + let rep = rep.into(); + self.unify_var_rep(this_var, rep)?; + rep + } else { + self.unify_var_rep(this_var, rep0)?; + rep0 + } + } + (rep0, rep1) => { + if let Some(rep) = cast_rep { + let rep = rep.into(); + self.unify_var_rep(this_var, rep)?; + if l_rep.is_auto() { + debug_assert!(!r_rep.is_auto()); + self.unify_var_pair(this_var, l_var)?; + } + if r_rep.is_auto() { + debug_assert!(!l_rep.is_auto()); + self.unify_var_pair(this_var, r_var)?; + } + rep + } else { + if l_rep.is_auto() { + debug_assert!(!r_rep.is_auto()); + self.unify_var_rep(this_var, rep1)?; + self.unify_var_rep(l_var, rep1)?; + rep1 + } else if r_rep.is_auto() { + self.unify_var_rep(this_var, rep0)?; + self.unify_var_rep(r_var, rep0)?; + rep0 + } else { + return Err(InferenceError::Ambiguous); + } + } + } + }; + (this_var, this_rep) + } + Expr::UnaryOp(unary_op, expr) => { + let this_var = self.get_new_uvar(); + let (inner_var, inner_rep) = self.infer_var_expr(&expr)?; + let cast_rep = unary_op.cast_rep(); + let this_rep = match inner_rep { + NumRep::AUTO => { + self.unify_var_pair(this_var, inner_var)?; + if let Some(rep) = cast_rep { + let rep = rep.into(); + self.unify_var_rep(this_var, rep)?; + rep + } else { + { + // REVIEW - do we need to go this far? + match e.eval() { + Ok(v) => match v.as_const() { + Some(c) => { + // NOTE - if there is a const-evaluable result for the computation, use it to refine our constraints on which types satisfy the aliased Auto + let bounds = + Bounds::singleton(c.as_raw_value().clone()); + self.unify_var_constraint( + this_var, + Constraint::Encompasses(bounds), + )?; + } + None => { + // FIXME - this isn't a hard error necessarily, but our model isn't complex enough for non-TypedConst values to emerge + unimplemented!("Value::AsConst returned None (unexpectedly) when called from InferenceEngine::infer_var_expr"); + } + }, + Err(e) => { + // NOTE - If the computation will fail regardless, there is no need to infer the type-information of the AST + // REVIEW - make sure that we are confident in EvalErrors being sound reasons to fail type-inference, both now and going forward + return Err(InferenceError::Eval(e)); + } + } + } + NumRep::AUTO + } + } + rep0 => { + if let Some(rep) = cast_rep { + let rep = rep.into(); + self.unify_var_rep(this_var, rep)?; + rep + } else { + self.unify_var_rep(this_var, rep0)?; + rep0 + } + } + }; + (this_var, this_rep) + } + &Expr::Cast(rep, ref expr) => { + let this_var = self.get_new_uvar(); + let (inner_var, inner_rep) = self.infer_var_expr(&expr)?; + let rep = rep.into(); + if inner_rep.is_auto() { + self.unify_var_rep(inner_var, rep)?; + } + self.unify_var_rep(this_var, rep)?; + (this_var, rep) + } + }; + Ok((top_var, top_rep)) + } + + fn to_whnf_vtype(&self, t: UType) -> VType { + match t { + UType::Var(v) => { + let v0 = self.get_canonical_uvar(v); + match &self.constraints[v0.0] { + Constraints::Indefinite => VType::Abstract(v0.into()), + Constraints::Invariant(Constraint::Equiv(ut)) => self.to_whnf_vtype(*ut), + Constraints::Invariant(Constraint::Encompasses(bounds)) => { + VType::Within(bounds.clone()) + } + } + } + UType::Int(int_type) => VType::Int(int_type), + } + } + + pub(crate) fn substitute_uvar_vtype(&self, v: UVar) -> InferenceResult> { + match &self.constraints[self.get_canonical_uvar(v).0] { + Constraints::Indefinite => Ok(None), + Constraints::Invariant(cx) => Ok(match cx { + Constraint::Equiv(ut) => Some(self.to_whnf_vtype(*ut)), + Constraint::Encompasses(bounds) => Some(VType::Within(bounds.clone())), + }), + } + } + } + + impl InferenceEngine { + pub fn reify(&self, t: UType) -> Option { + match t { + UType::Var(uv) => { + let v = self.get_canonical_uvar(uv); + match self.substitute_uvar_vtype(v) { + Ok(Some(t0)) => match t0 { + VType::Int(int_type) => Some(int_type), + VType::Within(bounds) => match Constraint::get_unique_solution( + &Constraint::Encompasses(bounds.clone()), + ) { + Ok(int_type) => Some(int_type), + Err(_) => None, + }, + VType::Abstract(utype) => self.reify(utype), + }, + Err(_) => None, + Ok(None) => match &self.constraints[v.0] { + _ => None, + }, + } + } + UType::Int(i) => Some(i), + } + } + } +} + +use inference::{InferenceEngine, UVar}; + +/// Alias for whatever value-type we use to associate a failed reification with some indication of what went wrong, or where +type Hint = usize; + +#[derive(Debug)] +pub enum ElaborationError { + BadReification(Hint), +} + +impl std::fmt::Display for ElaborationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ElaborationError::BadReification(hint) => { + write!(f, "bad reification on UVar ?{}", hint) + } + } + } +} + +impl std::error::Error for ElaborationError {} + +pub(crate) type ElaborationResult = Result; + +pub struct Elaborator { + next_index: usize, + ie: InferenceEngine, +} + +impl Elaborator { + pub(crate) fn new(ie: InferenceEngine) -> Self { + Self { next_index: 0, ie } + } + + fn get_and_increment_index(&mut self) -> usize { + let ret = self.next_index; + self.next_index += 1; + ret + } + + fn get_type_from_index(&self, index: usize) -> ElaborationResult { + let uvar = UVar::new(index); + let Some(t) = self.ie.reify(uvar.into()) else { + return Err(ElaborationError::BadReification(index)); + }; + Ok(t) + } + + pub(crate) fn elaborate_expr(&mut self, expr: &Expr) -> ElaborationResult> { + let index = self.get_and_increment_index(); + match expr { + Expr::Const(typed_const) => { + let t = self.get_type_from_index(index)?; + Ok(TypedExpr::ElabConst(t, typed_const.clone())) + } + Expr::BinOp(bin_op, x, y) => { + let t_x = self.elaborate_expr(x)?; + let t_y = self.elaborate_expr(y)?; + let t = self.get_type_from_index(index)?; + Ok(TypedExpr::ElabBinOp( + t, + TypedBinOp { + sig: ((*t_x.get_type(), *t_y.get_type()), t), + inner: *bin_op, + }, + Box::new(t_x), + Box::new(t_y), + )) + } + Expr::UnaryOp(unary_op, inner) => { + let t_inner = self.elaborate_expr(inner)?; + let t = self.get_type_from_index(index)?; + Ok(TypedExpr::ElabUnaryOp( + t, + TypedUnaryOp { + sig: (*t_inner.get_type(), t), + inner: *unary_op, + }, + Box::new(t_inner), + )) + } + &Expr::Cast(rep, ref inner) => { + let rep = NumRep::Concrete(rep); + let t_inner = self.elaborate_expr(inner)?; + let t = self.get_type_from_index(index)?; + Ok(TypedExpr::ElabCast( + t, + TypedCast { + sig: (*t_inner.get_type(), t), + _rep: rep, + }, + Box::new(t_inner), + )) + } + } + } +} diff --git a/experiments/analytic-engine/src/eval.rs b/experiments/analytic-engine/src/eval.rs new file mode 100644 index 00000000..0c71aed8 --- /dev/null +++ b/experiments/analytic-engine/src/eval.rs @@ -0,0 +1,858 @@ +use num_bigint::{BigInt, TryFromBigIntError}; +use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedRem, CheckedSub, Signed, Zero}; +use std::any::type_name; +use std::cell::LazyCell; +use std::ops::Neg; +use std::rc::Rc; + +// SECTION - Evaluation model for arbitrary operations over machine integer types +#[derive(Clone)] +pub struct IndirectEval { + value: Rc BigInt>>>, +} + +impl std::fmt::Debug for IndirectEval { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IndirectEval") + .field("value", &self.value) + .finish() + } +} + +#[derive(Clone, Debug)] +pub enum Eval { + NaN, + Direct(T), + Indirect(IndirectEval), +} + +impl Eval { + pub const fn is_nan(&self) -> bool { + matches!(self, &Eval::NaN) + } +} + +impl PartialEq for Eval +where + T: PartialEq + Copy, + BigInt: From, +{ + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::NaN, _) | (_, Self::NaN) => false, + (Self::Direct(l0), Self::Direct(r0)) => l0 == r0, + (Self::Indirect(l0), Self::Indirect(r0)) => &**l0.value == &**r0.value, + (Self::Direct(n), Self::Indirect(IndirectEval { ref value, .. })) + | (Self::Indirect(IndirectEval { ref value, .. }), Self::Direct(n)) => { + &***value == &BigInt::from(*n) + } + } + } +} + +pub enum EvalError { + Indirect(BigInt), + NotANumber, +} + +impl Eval { + pub fn eval(&self) -> Result { + match self { + Eval::NaN => Err(EvalError::NotANumber), + Eval::Direct(x) => Ok(*x), + Eval::Indirect(IndirectEval { value, .. }) => { + Err(EvalError::Indirect((&***value).clone())) + } + } + } +} + +// !SECTION + +// SECTION - Binary Operation Backend + +/// Macro for bulk definition of homogenously typed binary operations with a checked version provided by a trait (`num_traits`), where +/// `None`` indicates underflow or overflow, falling back on available saturating and wrapping variants of the same operation for indirect computations +macro_rules! homogenous { + ( $( $tr:ident, $meth:ident => $( ( $fname:ident , $t:ty ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + pub fn $fname(lhs: $t, rhs: $t) -> Eval<$t> { + match <$t as $tr>::$meth(&lhs, &rhs) { + Some(res) => Eval::Direct(res), + None => { + Eval::Indirect( + IndirectEval { + value: Rc::new(LazyCell::new( + Box::new(move || + BigInt::from(lhs).$meth(&BigInt::from(rhs)).unwrap() + ) + )), + } + ) + } + } + } + )* + )* + }; +} + +/// Macro for bulk definition of homogenously typed binary operations with a quotient version provided by a trait (`num_traits`), where None indicates NaN +macro_rules! homogenous_quotient { + ( $( $tr:ident, $meth:ident => $( ( $fname:ident, $t:ty ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + pub fn $fname(lhs: $t, rhs: $t) -> Eval<$t> { + match <$t as $tr>::$meth(&lhs, &rhs) { + Some(res) => Eval::Direct(res), + None => Eval::NaN, + } + } + )* + )* + } +} + +/// Macro for bulk definition of homogenous source-type typed binary operations where the output type can represent every possible input type, but the computation might not succeed +macro_rules! widening { + ( $( $tr:ident, $meth:ident => $( ( $in_t:ty => $( ( $fname:ident, $out_t:ty ) ),+ $(,)? ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + $( + pub fn $fname(lhs: $in_t, rhs: $in_t) -> Eval<$out_t> { + match <$out_t as $tr>::$meth(&(lhs as $out_t), &(rhs as $out_t)) { + Some(res) => Eval::Direct(res), + None => Eval::Indirect(IndirectEval { value: Rc::new(LazyCell::new(Box::new(move || BigInt::$meth(&BigInt::from(lhs), &BigInt::from(rhs)).unwrap()))) }), + } + } + )* + )* + )* + }; +} + +macro_rules! widening_quotient { + ( $( $tr:ident, $meth:ident => $( ( $in_t:ty => $( ( $fname:ident, $out_t:ty ) ),+ $(,)? ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + $( + pub fn $fname(lhs: $in_t, rhs: $in_t) -> Eval<$out_t> { + match <$out_t as $tr>::$meth(&(lhs as $out_t), &(rhs as $out_t)) { + Some(res) => Eval::Direct(res), + None => Eval::NaN, + } + } + )* + )* + )* + }; +} + +/// Macro for bulk definition of heterogenous source-type typed binary operations where the output type can represent every possible input type, but the computation might not succeed +macro_rules! mixed_widening { + ( $( $tr:ident, $meth:ident => $( ( $left_t:ty, $right_t:ty => $( ( $fname:ident, $out_t:ty ) ),+ $(,)? ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + $( + pub fn $fname(lhs: $left_t, rhs: $right_t) -> Eval<$out_t> { + match <$out_t as $tr>::$meth(&(lhs as $out_t), &(rhs as $out_t)) { + Some(res) => Eval::Direct(res), + None => Eval::Indirect(IndirectEval { value: Rc::new(LazyCell::new(Box::new(move || BigInt::$meth(&BigInt::from(lhs), &BigInt::from(rhs)).unwrap()))) }), + } + } + )* + )* + )* + }; +} + +macro_rules! mixed_widening_quotient { + ( $( $tr:ident, $meth:ident => $( ( $left_t:ty, $right_t:ty => $( ( $fname:ident, $out_t:ty ) ),+ $(,)? ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + $( + pub fn $fname(lhs: $left_t, rhs: $right_t) -> Eval<$out_t> { + match <$out_t as $tr>::$meth(&(lhs as $out_t), &(rhs as $out_t)) { + Some(res) => Eval::Direct(res), + None => Eval::NaN, + } + } + )* + )* + )* + }; +} + +// SECTION - strictly homogenous (T -> T -> T) binary operations (40 total) + +// Responsible for 24 function definitions +homogenous! { + CheckedAdd, checked_add => + (add_u8, u8), + (add_u16, u16), + (add_u32, u32), + (add_u64, u64), + (add_i8, i8), + (add_i16, i16), + (add_i32, i32), + (add_i64, i64); + CheckedSub, checked_sub => + (sub_u8, u8), + (sub_u16, u16), + (sub_u32, u32), + (sub_u64, u64), + (sub_i8, i8), + (sub_i16, i16), + (sub_i32, i32), + (sub_i64, i64); + CheckedMul, checked_mul => + (mul_u8, u8), + (mul_u16, u16), + (mul_u32, u32), + (mul_u64, u64), + (mul_i8, i8), + (mul_i16, i16), + (mul_i32, i32), + (mul_i64, i64); +} + +// (T, T) -> T binary operations involving quotients (failure is NaN) +// Responsible for 16 function definitions +homogenous_quotient! { + CheckedDiv, checked_div => + (div_u8, u8), + (div_u16, u16), + (div_u32, u32), + (div_u64, u64), + (div_i8, i8), + (div_i16, i16), + (div_i32, i32), + (div_i64, i64); + CheckedRem, checked_rem => + (rem_u8, u8), + (rem_u16, u16), + (rem_u32, u32), + (rem_u64, u64), + (rem_i8, i8), + (rem_i16, i16), + (rem_i32, i32), + (rem_i64, i64); +} +// !SECTION + +// SECTION - heterogenous-type binary operations ( `(T0, T0) -> T1` or `(T0, T1) -> T` ) +// (T0, T0) -> T1 binary operations where the values of T0 are a subset of T1 and failure indicates overflow/underflow + +// responsible for 42 function definitions +widening! { + CheckedAdd, checked_add => + (u8 => (add_u8_u16, u16), (add_u8_u32, u32), (add_u8_u64, u64), (add_u8_i16, i16), (add_u8_i32, i32), (add_u8_i64, i64)), + (u16 => (add_u16_u32, u32), (add_u16_u64, u64), (add_u16_i32, i32), (add_u16_i64, i64)), + (u32 => (add_u32_u64, u64), (add_u32_i64, i64)), + (i8 => (add_i8_i16, i16), (add_i8_i32, i32), (add_i8_i64, i64)), + (i16 => (add_i16_i32, i32), (add_i16_i64, i64)), + (i32 => (add_i32_i64, i64)); + CheckedSub, checked_sub => + (u8 => (sub_u8_u16, u16), (sub_u8_u32, u32), (sub_u8_u64, u64), (sub_u8_i16, i16), (sub_u8_i32, i32), (sub_u8_i64, i64)), + (u16 => (sub_u16_u32, u32), (sub_u16_u64, u64), (sub_u16_i32, i32), (sub_u16_i64, i64)), + (u32 => (sub_u32_u64, u64), (sub_u32_i64, i64)), + (i8 => (sub_i8_i16, i16), (sub_i8_i32, i32), (sub_i8_i64, i64)), + (i16 => (sub_i16_i32, i32), (sub_i16_i64, i64)), + (i32 => (sub_i32_i64, i64)); + CheckedMul, checked_mul => + (u8 => (mul_u8_u16, u16), (mul_u8_u32, u32), (mul_u8_u64, u64), (mul_u8_i16, i16), (mul_u8_i32, i32), (mul_u8_i64, i64)), + (u16 => (mul_u16_u32, u32), (mul_u16_u64, u64), (mul_u16_i32, i32), (mul_u16_i64, i64)), + (u32 => (mul_u32_u64, u64), (mul_u32_i64, i64)), + (i8 => (mul_i8_i16, i16), (mul_i8_i32, i32), (mul_i8_i64, i64)), + (i16 => (mul_i16_i32, i32), (mul_i16_i64, i64)), + (i32 => (mul_i32_i64, i64)); +} + +// (T0, T0) -> T1 binary operations where the values of T0 are a subset of T1 and failure indicates NaN +// Responsible for 28 function definitions +widening_quotient! { + CheckedDiv, checked_div => + // 14 functions + (u8 => (div_u8_u16, u16), (div_u8_u32, u32), (div_u8_u64, u64), (div_u8_i16, i16), (div_u8_i32, i32), (div_u8_i64, i64)), + (u16 => (div_u16_u32, u32), (div_u16_u64, u64), (div_u16_i32, i32), (div_u16_i64, i64)), + (u32 => (div_u32_u64, u64), (div_u32_i64, i64)), + (i8 => (div_i8_i16, i16), (div_i8_i32, i32), (div_i8_i64, i64)), + (i16 => (div_i16_i32, i32), (div_i16_i64, i64)), + (i32 => (div_i32_i64, i64)); + CheckedRem, checked_rem => + // 14 functions + (u8 => (rem_u8_u16, u16), (rem_u8_u32, u32), (rem_u8_u64, u64), (rem_u8_i16, i16), (rem_u8_i32, i32), (rem_u8_i64, i64)), // 6 + (u16 => (rem_u16_u32, u32), (rem_u16_u64, u64), (rem_u16_i32, i32), (rem_u16_i64, i64)), // 4 + (u32 => (rem_u32_u64, u64), (rem_u32_i64, i64)), // 2 + (i8 => (rem_i8_i16, i16), (rem_i8_i32, i32), (rem_i8_i64, i64)), // 3 + (i16 => (rem_i16_i32, i32), (rem_i16_i64, i64)), // 2 + (i32 => (rem_i32_i64, i64)); // 1 +} + +// (T0, T1) -> T binary operations +// Responsible for 210 functions +mixed_widening! { + CheckedAdd, checked_add => + // <= Bits8 (6 functions) + (u8, i8 => (add_u8_i8_i16, i16), (add_u8_i8_i32, i32), (add_u8_i8_i64, i64)), + (i8, u8 => (add_i8_u8_i16, i16), (add_i8_u8_i32, i32), (add_i8_u8_i64, i64)), + + // >Bits8, <= Bits16 (30 functions) + (u16, u8 => (add_u16_u8_u16, u16), (add_u16_u8_u32, u32), (add_u16_u8_u64, u64), (add_u16_u8_i32, i32), (add_u16_u8_i64, i64)), + (u8, u16 => (add_u8_u16_u16, u16), (add_u8_u16_u32, u32), (add_u8_u16_u64, u64), (add_u8_u16_i32, i32), (add_u8_u16_i64, i64)), + (u16, i8 => (add_u16_i8_i32, i32), (add_u16_i8_i64, i64)), + (i8, u16 => (add_i8_u16_i32, i32), (add_i8_u16_i64, i64)), + + (i16, u8 => (add_i16_u8_i16, i16), (add_i16_u8_i32, i32), (add_i16_u8_i64, i64)), + (u8, i16 => (add_u8_i16_i16, i16), (add_u8_i16_i32, i32), (add_u8_i16_i64, i64)), + (i16, i8 => (add_i16_i8_i16, i16), (add_i16_i8_i32, i32), (add_i16_i8_i64, i64)), + (i8, i16 => (add_i8_i16_i16, i16), (add_i8_i16_i32, i32), (add_i8_i16_i64, i64)), + + (u16, i16 => (add_u16_i16_i32, i32), (add_u16_i16_i64, i64)), + (i16, u16 => (add_i16_u16_i32, i32), (add_i16_u16_i64, i64)), + + // >Bits16, <=Bits32 (34 functions) + (u32, u8 => (add_u32_u8_u32, u32), (add_u32_u8_u64, u64), (add_u32_u8_i64, i64)), + (u8, u32 => (add_u8_u32_u32, u32), (add_u8_u32_u64, u64), (add_u8_u32_i64, i64)), + (u32, i8 => (add_u32_i8_i64, i64)), + (i8, u32 => (add_i8_u32_i64, i64)), + + (u32, u16 => (add_u32_u16_u32, u32), (add_u32_u16_u64, u64), (add_u32_u16_i64, i64)), + (u16, u32 => (add_u16_u32_u32, u32), (add_u16_u32_u64, u64), (add_u16_u32_i64, i64)), + (u32, i16 => (add_u32_i16_i64, i64)), + (i16, u32 => (add_i16_u32_i64, i64)), + + (i32, u8 => (add_i32_u8_i32, i32), (add_i32_u8_i64, i64)), + (u8, i32 => (add_u8_i32_i32, i32), (add_u8_i32_i64, i64)), + (i32, i8 => (add_i32_i8_i32, i32), (add_i32_i8_i64, i64)), + (i8, i32 => (add_i8_i32_i32, i32), (add_i8_i32_i64, i64)), + + (i32, u16 => (add_i32_u16_i32, i32), (add_i32_u16_i64, i64)), + (u16, i32 => (add_u16_i32_i32, i32), (add_u16_i32_i64, i64)), + (i32, i16 => (add_i32_i16_i32, i32), (add_i32_i16_i64, i64)), + (i16, i32 => (add_i16_i32_i32, i32), (add_i16_i32_i64, i64)), + + (u32, i32 => (add_u32_i32_i64, i64)), + (i32, u32 => (add_i32_u32_i64, i64)), + ; + CheckedSub, checked_sub => + // <= Bits8 (6 functions) + (u8, i8 => (sub_u8_i8_i16, i16), (sub_u8_i8_i32, i32), (sub_u8_i8_i64, i64)), + (i8, u8 => (sub_i8_u8_i16, i16), (sub_i8_u8_i32, i32), (sub_i8_u8_i64, i64)), + + // >Bits8, <= Bits16 (30 functions) + (u16, u8 => (sub_u16_u8_u16, u16), (sub_u16_u8_u32, u32), (sub_u16_u8_u64, u64), (sub_u16_u8_i32, i32), (sub_u16_u8_i64, i64)), + (u8, u16 => (sub_u8_u16_u16, u16), (sub_u8_u16_u32, u32), (sub_u8_u16_u64, u64), (sub_u8_u16_i32, i32), (sub_u8_u16_i64, i64)), + (u16, i8 => (sub_u16_i8_i32, i32), (sub_u16_i8_i64, i64)), + (i8, u16 => (sub_i8_u16_i32, i32), (sub_i8_u16_i64, i64)), + + (u8, i16 => (sub_u8_i16_i16, i16), (sub_u8_i16_i32, i32), (sub_u8_i16_i64, i64)), + (i16, u8 => (sub_i16_u8_i16, i16), (sub_i16_u8_i32, i32), (sub_i16_u8_i64, i64)), + (i8, i16 => (sub_i8_i16_i16, i16), (sub_i8_i16_i32, i32), (sub_i8_i16_i64, i64)), + (i16, i8 => (sub_i16_i8_i16, i16), (sub_i16_i8_i32, i32), (sub_i16_i8_i64, i64)), + + (u16, i16 => (sub_u16_i16_i32, i32), (sub_u16_i16_i64, i64)), + (i16, u16 => (sub_i16_u16_i32, i32), (sub_i16_u16_i64, i64)), + + // >Bits16, <= Bits32 (34 functions) + (u32, u8 => (sub_u32_u8_u32, u32), (sub_u32_u8_u64, u64), (sub_u32_u8_i64, i64)), + (u8, u32 => (sub_u8_u32_u32, u32), (sub_u8_u32_u64, u64), (sub_u8_u32_i64, i64)), + (u32, i8 => (sub_u32_i8_i64, i64)), + (i8, u32 => (sub_i8_u32_i64, i64)), + + (u32, u16 => (sub_u32_u16_u32, u32), (sub_u32_u16_u64, u64), (sub_u32_u16_i64, i64)), + (u16, u32 => (sub_u16_u32_u32, u32), (sub_u16_u32_u64, u64), (sub_u16_u32_i64, i64)), + (u32, i16 => (sub_u32_i16_i64, i64)), + (i16, u32 => (sub_i16_u32_i64, i64)), + + (i32, u8 => (sub_i32_u8_i32, i32), (sub_i32_u8_i64, i64)), + (u8, i32 => (sub_u8_i32_i32, i32), (sub_u8_i32_i64, i64)), + (i32, i8 => (sub_i32_i8_i32, i32), (sub_i32_i8_i64, i64)), + (i8, i32 => (sub_i8_i32_i32, i32), (sub_i8_i32_i64, i64)), + + (i32, u16 => (sub_i32_u16_i32, i32), (sub_i32_u16_i64, i64)), + (u16, i32 => (sub_u16_i32_i32, i32), (sub_u16_i32_i64, i64)), + (i32, i16 => (sub_i32_i16_i32, i32), (sub_i32_i16_i64, i64)), + (i16, i32 => (sub_i16_i32_i32, i32), (sub_i16_i32_i64, i64)), + + (u32, i32 => (sub_u32_i32_i64, i64)), + (i32, u32 => (sub_i32_u32_i64, i64)); + CheckedMul, checked_mul => + // <= Bits8 (6 functions) + (u8, i8 => (mul_u8_i8_i16, i16), (mul_u8_i8_i32, i32), (mul_u8_i8_i64, i64)), + (i8, u8 => (mul_i8_u8_i16, i16), (mul_i8_u8_i32, i32), (mul_i8_u8_i64, i64)), + + // >Bits8, <= Bits16 (30 functions) + (u16, u8 => (mul_u16_u8_u16, u16), (mul_u16_u8_u32, u32), (mul_u16_u8_u64, u64), (mul_u16_u8_i32, i32), (mul_u16_u8_i64, i64)), + (u8, u16 => (mul_u8_u16_u16, u16), (mul_u8_u16_u32, u32), (mul_u8_u16_u64, u64), (mul_u8_u16_i32, i32), (mul_u8_u16_i64, i64)), + (u16, i8 => (mul_u16_i8_i32, i32), (mul_u16_i8_i64, i64)), + (i8, u16 => (mul_i8_u16_i32, i32), (mul_i8_u16_i64, i64)), + + (u8, i16 => (mul_u8_i16_i16, i16), (mul_u8_i16_i32, i32), (mul_u8_i16_i64, i64)), + (i16, u8 => (mul_i16_u8_i16, i16), (mul_i16_u8_i32, i32), (mul_i16_u8_i64, i64)), + (i8, i16 => (mul_i8_i16_i16, i16), (mul_i8_i16_i32, i32), (mul_i8_i16_i64, i64)), + (i16, i8 => (mul_i16_i8_i16, i16), (mul_i16_i8_i32, i32), (mul_i16_i8_i64, i64)), + + (u16, i16 => (mul_u16_i16_i32, i32), (mul_u16_i16_i64, i64)), + (i16, u16 => (mul_i16_u16_i32, i32), (mul_i16_u16_i64, i64)), + + // >Bits16, <= Bits32 (34 functions) + (u32, u8 => (mul_u32_u8_u32, u32), (mul_u32_u8_u64, u64), (mul_u32_u8_i64, i64)), + (u8, u32 => (mul_u8_u32_u32, u32), (mul_u8_u32_u64, u64), (mul_u8_u32_i64, i64)), + (u32, i8 => (mul_u32_i8_i64, i64)), + (i8, u32 => (mul_i8_u32_i64, i64)), + + (u32, u16 => (mul_u32_u16_u32, u32), (mul_u32_u16_u64, u64), (mul_u32_u16_i64, i64)), + (u16, u32 => (mul_u16_u32_u32, u32), (mul_u16_u32_u64, u64), (mul_u16_u32_i64, i64)), + (u32, i16 => (mul_u32_i16_i64, i64)), + (i16, u32 => (mul_i16_u32_i64, i64)), + + (i32, u8 => (mul_i32_u8_i32, i32), (mul_i32_u8_i64, i64)), + (u8, i32 => (mul_u8_i32_i32, i32), (mul_u8_i32_i64, i64)), + (i32, i8 => (mul_i32_i8_i32, i32), (mul_i32_i8_i64, i64)), + (i8, i32 => (mul_i8_i32_i32, i32), (mul_i8_i32_i64, i64)), + + (i32, u16 => (mul_i32_u16_i32, i32), (mul_i32_u16_i64, i64)), + (u16, i32 => (mul_u16_i32_i32, i32), (mul_u16_i32_i64, i64)), + (i32, i16 => (mul_i32_i16_i32, i32), (mul_i32_i16_i64, i64)), + (i16, i32 => (mul_i16_i32_i32, i32), (mul_i16_i32_i64, i64)), + + (u32, i32 => (mul_u32_i32_i64, i64)), + (i32, u32 => (mul_i32_u32_i64, i64)); +} + +// Responsible for 140 functions +mixed_widening_quotient! { + CheckedDiv, checked_div => + // <= Bits8 (6 functions) + (u8, i8 => (div_u8_i8_i16, i16), (div_u8_i8_i32, i32), (div_u8_i8_i64, i64)), + (i8, u8 => (div_i8_u8_i16, i16), (div_i8_u8_i32, i32), (div_i8_u8_i64, i64)), + + // >Bits8, <= Bits16 (30 functions) + (u16, u8 => (div_u16_u8_u16, u16), (div_u16_u8_u32, u32), (div_u16_u8_u64, u64), (div_u16_u8_i32, i32), (div_u16_u8_i64, i64)), + (u8, u16 => (div_u8_u16_u16, u16), (div_u8_u16_u32, u32), (div_u8_u16_u64, u64), (div_u8_u16_i32, i32), (div_u8_u16_i64, i64)), + (u16, i8 => (div_u16_i8_i32, i32), (div_u16_i8_i64, i64)), + (i8, u16 => (div_i8_u16_i32, i32), (div_i8_u16_i64, i64)), + + (i16, u8 => (div_i16_u8_i16, i16), (div_i16_u8_i32, i32), (div_i16_u8_i64, i64)), + (u8, i16 => (div_u8_i16_i16, i16), (div_u8_i16_i32, i32), (div_u8_i16_i64, i64)), + (i16, i8 => (div_i16_i8_i16, i16), (div_i16_i8_i32, i32), (div_i16_i8_i64, i64)), + (i8, i16 => (div_i8_i16_i16, i16), (div_i8_i16_i32, i32), (div_i8_i16_i64, i64)), + + (u16, i16 => (div_u16_i16_i32, i32), (div_u16_i16_i64, i64)), + (i16, u16 => (div_i16_u16_i32, i32), (div_i16_u16_i64, i64)), + + // >Bits16, <=Bits32 (34 functions) + (u32, u8 => (div_u32_u8_u32, u32), (div_u32_u8_u64, u64), (div_u32_u8_i64, i64)), + (u8, u32 => (div_u8_u32_u32, u32), (div_u8_u32_u64, u64), (div_u8_u32_i64, i64)), + (u32, i8 => (div_u32_i8_i64, i64)), + (i8, u32 => (div_i8_u32_i64, i64)), + + (u32, u16 => (div_u32_u16_u32, u32), (div_u32_u16_u64, u64), (div_u32_u16_i64, i64)), + (u16, u32 => (div_u16_u32_u32, u32), (div_u16_u32_u64, u64), (div_u16_u32_i64, i64)), + (u32, i16 => (div_u32_i16_i64, i64)), + (i16, u32 => (div_i16_u32_i64, i64)), + + (i32, u8 => (div_i32_u8_i32, i32), (div_i32_u8_i64, i64)), + (u8, i32 => (div_u8_i32_i32, i32), (div_u8_i32_i64, i64)), + (i32, i8 => (div_i32_i8_i32, i32), (div_i32_i8_i64, i64)), + (i8, i32 => (div_i8_i32_i32, i32), (div_i8_i32_i64, i64)), + + (i32, u16 => (div_i32_u16_i32, i32), (div_i32_u16_i64, i64)), + (u16, i32 => (div_u16_i32_i32, i32), (div_u16_i32_i64, i64)), + (i32, i16 => (div_i32_i16_i32, i32), (div_i32_i16_i64, i64)), + (i16, i32 => (div_i16_i32_i32, i32), (div_i16_i32_i64, i64)), + + + (u32, i32 => (div_u32_i32_i64, i64)), + (i32, u32 => (div_i32_u32_i64, i64)), + ; + CheckedRem, checked_rem => + // <= Bits8 (6 functions) + (u8, i8 => (rem_u8_i8_i16, i16), (rem_u8_i8_i32, i32), (rem_u8_i8_i64, i64)), + (i8, u8 => (rem_i8_u8_i16, i16), (rem_i8_u8_i32, i32), (rem_i8_u8_i64, i64)), + + // >Bits8, <= Bits16 (30 functions) + (u16, u8 => (rem_u16_u8_u16, u16), (rem_u16_u8_u32, u32), (rem_u16_u8_u64, u64), (rem_u16_u8_i32, i32), (rem_u16_u8_i64, i64)), + (u8, u16 => (rem_u8_u16_u16, u16), (rem_u8_u16_u32, u32), (rem_u8_u16_u64, u64), (rem_u8_u16_i32, i32), (rem_u8_u16_i64, i64)), + (u16, i8 => (rem_u16_i8_i32, i32), (rem_u16_i8_i64, i64)), + (i8, u16 => (rem_i8_u16_i32, i32), (rem_i8_u16_i64, i64)), + + (u8, i16 => (rem_u8_i16_i16, i16), (rem_u8_i16_i32, i32), (rem_u8_i16_i64, i64)), + (i16, u8 => (rem_i16_u8_i16, i16), (rem_i16_u8_i32, i32), (rem_i16_u8_i64, i64)), + (i8, i16 => (rem_i8_i16_i16, i16), (rem_i8_i16_i32, i32), (rem_i8_i16_i64, i64)), + (i16, i8 => (rem_i16_i8_i16, i16), (rem_i16_i8_i32, i32), (rem_i16_i8_i64, i64)), + + (u16, i16 => (rem_u16_i16_i32, i32), (rem_u16_i16_i64, i64)), + (i16, u16 => (rem_i16_u16_i32, i32), (rem_i16_u16_i64, i64)), + + // >Bits16, <= Bits32 (34 functions) + (u32, u8 => (rem_u32_u8_u32, u32), (rem_u32_u8_u64, u64), (rem_u32_u8_i64, i64)), + (u8, u32 => (rem_u8_u32_u32, u32), (rem_u8_u32_u64, u64), (rem_u8_u32_i64, i64)), + (u32, i8 => (rem_u32_i8_i64, i64)), + (i8, u32 => (rem_i8_u32_i64, i64)), + + (u32, u16 => (rem_u32_u16_u32, u32), (rem_u32_u16_u64, u64), (rem_u32_u16_i64, i64)), + (u16, u32 => (rem_u16_u32_u32, u32), (rem_u16_u32_u64, u64), (rem_u16_u32_i64, i64)), + (u32, i16 => (rem_u32_i16_i64, i64)), + (i16, u32 => (rem_i16_u32_i64, i64)), + + (i32, u8 => (rem_i32_u8_i32, i32), (rem_i32_u8_i64, i64)), + (u8, i32 => (rem_u8_i32_i32, i32), (rem_u8_i32_i64, i64)), + (i32, i8 => (rem_i32_i8_i32, i32), (rem_i32_i8_i64, i64)), + (i8, i32 => (rem_i8_i32_i32, i32), (rem_i8_i32_i64, i64)), + + (i32, u16 => (rem_i32_u16_i32, i32), (rem_i32_u16_i64, i64)), + (u16, i32 => (rem_u16_i32_i32, i32), (rem_u16_i32_i64, i64)), + (i32, i16 => (rem_i32_i16_i32, i32), (rem_i32_i16_i64, i64)), + (i16, i32 => (rem_i16_i32_i32, i32), (rem_i16_i32_i64, i64)), + + (u32, i32 => (rem_u32_i32_i64, i64)), + (i32, u32 => (rem_i32_u32_i64, i64)); +} +// !SECTION + +// !SECTION + +pub fn eval_fallback( + lhs: Lhs, + rhs: Rhs, + _op_hint: &'static str, + checked_op: impl FnOnce(&BigInt, &BigInt) -> Option, +) -> Eval +where + BigInt: From + From, + Res: TryFrom>, +{ + eprintln!("[INFO]: encountered fallback operation `{_op_hint} : (({}, {}) -> {})` that may benefit from standalone function", + type_name::(), type_name::(), type_name::() + ); + let big_l = BigInt::from(lhs); + let big_r = BigInt::from(rhs); + let o_big_res = checked_op(&big_l, &big_r); + if let Some(big_res) = o_big_res { + match >::try_from(big_res) { + Ok(res) => Eval::Direct(res), + Err(e) => Eval::Indirect(IndirectEval { + value: Rc::new(LazyCell::new(Box::new(move || e.into_original()))), + }), + } + } else { + Eval::NaN + } +} + +// !SECTION + +// SECTION - Unary Operation Backend + +/// Macro for negating via the `Neg` trait impl on a signed integer output type +macro_rules! negate_as_signed { + ( $( $t:ty, $fname:ident );+ $(;)? ) => { + $( + pub fn $fname(x: $t) -> Eval<$t> { + Eval::Direct(<$t as Neg>::neg(x)) + } + )* + }; + ( $( $t0:ty => $( ( $fname:ident, $t1:ty ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + pub fn $fname(x: $t0) -> Eval<$t1> { + Eval::Direct(<$t1 as Neg>::neg(x as $t1)) + } + )+ + )+ + }; +} + +/// Macro for negating when the output type is unsigned +/// +/// Contains rules for unsigned inputs (i.e. Indirect if non-zero, zero otherwise) +/// as well as for arbitrary inputs via signed intermediate representations (i.e. Indirect if positive, negation otherwise) +macro_rules! negate_into_unsigned { + // If positive, return Indirect immediately + // Check if positive (and therefore has no representable result), otherwise take absolute value and cast + ( $( $t:ty => $( ( $fname:ident, $out:ty ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + pub fn $fname(x: $t) -> Eval<$out> { + if <$t as ::num_traits::Signed>::is_positive(&x) { + Eval::Indirect(IndirectEval { value: Rc::new(LazyCell::new(Box::new(move || BigInt::from(x).neg()))) }) + } else { + if x == <$t>::MIN { + // FIXME - this should work, but is there a more elegant way of doing this? + Eval::Direct(<$t>::MAX as $out + 1) + } else { + Eval::Direct(x.neg() as $out) + } + } + } + )* + )* + }; +} + +// NOTE - This is the only macro which can specify narrowing conversions since 0 is the only representable value regardless of width +macro_rules! negate_unsigned_unsigned { + ( $( $t:ty => $( ( $fname:ident, $out:ty ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + pub fn $fname(x: $t) -> Eval<$out> { + if x.is_zero() { + Eval::Direct(<$out as Zero>::zero()) + } else { + Eval::Indirect(IndirectEval { value: Rc::new(LazyCell::new(Box::new(move || ::num_bigint::BigInt::from(x).neg()))) }) + } + } + )* + )* + }; +} + +/// Macro for abs via the `Signed` trait impl on a signed integer output type +macro_rules! abs_as_signed { + ( $( $t:ty, $fname:ident );+ $(;)? ) => { + $( pub fn $fname(x: $t) -> Eval<$t> { + let tmp = <$t as ::num_traits::Signed>::abs(&x); + // NOTE - Signed::abs(iX::MIN) == iX::MIN + if tmp == <$t>::MIN { + Eval::Indirect(IndirectEval { value: Rc::new(LazyCell::new(Box::new(move || ::num_bigint::BigInt::from(x).abs()))) }) + } else { + Eval::Direct(tmp) + } + } )+ + }; + ( $( $t:ty => $( ( $fname:ident, $t1:ty ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + pub fn $fname(x: $t) -> Eval<$t1> { + let tmp = <$t1 as ::num_traits::Signed>::abs(&(x as $t1)); + // We don't need to check this because the cast from $t cannot yield <$t1>::MIN + Eval::Direct(tmp) + } + )* + )* + }; +} + +/// Macro for taking absolute value via `Signed` impl on input followed by as-cast +macro_rules! abs_signed_unsigned { + ( $( $t:ty => $( ( $fname:ident, $t1:ty) ),+ $(,)? );+ $(;)? ) => { + $( + $( + pub fn $fname(x: $t) -> Eval<$t1> { + let tmp = <$t as ::num_traits::Signed>::abs(&x); + if tmp == <$t>::MIN { + Eval::Direct(<$t>::MAX as $t1 + 1) + } else { + Eval::Direct(tmp as $t1) + } + } + )+ + )+ + }; +} + +/// Any 'abs' function that is operationally equivalent to a (safe) cast (i.e. unsigned input and wider output type) +macro_rules! abs_unsigned_unsigned { + ( $( $t:ty, $fname:ident );+ $(;)? ) => { + $( + #[inline] + pub const fn $fname(x: $t) -> Eval<$t> { + Eval::Direct(x) + } + )+ + }; + ( $( $t:ty => $( ( $fname:ident, $t1:ty ) ),+ $(,)? );+ $(;)? ) => { + $( + $( + #[inline] + pub fn $fname(x: $t) -> Eval<$t1> { + Eval::Direct(x as $t1) + } + )+ + )+ + }; +} + +// SECTION - Negation Functions + +// Type-preserving negations on signed integers (4 functions) +negate_as_signed! { + i8, neg_i8; + i16, neg_i16; + i32, neg_i32; + i64, neg_i64; +} + +// Negations into signed integers (12 functions) +negate_as_signed! { + u8 => (neg_u8_i16, i16), (neg_u8_i32, i32), (neg_u8_i64, i64); + u16 => (neg_u16_i32, i32), (neg_u16_i64, i64); + u32 => (neg_u32_i64, i64); + i8 => (neg_i8_i16, i16), (neg_i8_i32, i32), (neg_i8_i64, i64); + i16 => (neg_i16_i32, i32), (neg_i16_i64, i64); + i32 => (neg_i32_i64, i64); +} + +// Sign-preserving negations between unsigned integers (16 functions) +negate_unsigned_unsigned! { + u8 => (neg_u8, u8), (neg_u8_u16, u16), (neg_u8_u32, u32), (neg_u8_u64, u64); + u16 => (neg_u16_u8, u8), (neg_u16, u16), (neg_u16_u32, u32), (neg_u16_u64, u64); + u32 => (neg_u32_u8, u8), (neg_u32_u16, u16), (neg_u32, u32), (neg_u32_u64, u64); + u64 => (neg_u64_u8, u8), (neg_u64_u16, u16), (neg_u64_u32, u32), (neg_u64, u64); +} + +// Negation from signed integers into unsigned integers (10 functions) +negate_into_unsigned! { + i8 => (neg_i8_u8, u8), (neg_i8_u16, u16), (neg_i8_u32, u32), (neg_i8_u64, u64); + i16 => (neg_i16_u16, u16), (neg_i16_u32, u32), (neg_i16_u64, u64); + i32 => (neg_i32_u32, u32), (neg_i32_u64, u64); + i64 => (neg_i64_u64, u64); +} + +/* 22 omitted negation functions involving lossy conversion + * - u64 -> T != uX (4) + * - u32 -> T < i64, != uX (3) + * - u16 -> T < i32, != uX (2) + * - u8 -> i8 (1) + * - i64 -> T < ?64 (6) + * - i32 -> T < ?32 (4) + * - i16 -> T < ?16 (2) + */ + +// !SECTION + +// SECTION - Absolute-Value Functions + +// Type-preserving abs on signed integers (4 functions) +abs_as_signed! { + i8, abs_i8; + i16, abs_i16; + i32, abs_i32; + i64, abs_i64; +} + +// Abs into signed integers (12 functions) +abs_as_signed! { + u8 => (abs_u8_i16, i16), (abs_u8_i32, i32), (abs_u8_i64, i64); + u16 => (abs_u16_i32, i32), (abs_u16_i64, i64); + u32 => (abs_u32_i64, i64); + i8 => (abs_i8_i16, i16), (abs_i8_i32, i32), (abs_i8_i64, i64); + i16 => (abs_i16_i32, i32), (abs_i16_i64, i64); + i32 => (abs_i32_i64, i64); +} + +// Abs from signed into unsigned integers (10 functions) +abs_signed_unsigned! { + i8 => (abs_i8_u8, u8), (abs_i8_u16, u16), (abs_i8_u32, u32), (abs_i8_u64, u64); + i16 => (abs_i16_u16, u16), (abs_i16_u32, u32), (abs_i16_u64, u64); + i32 => (abs_i32_u32, u32), (abs_i32_u64, u64); + i64 => (abs_i64_u64, u64); +} + +// Type-preserving abs on unsigned integers (4 functions) +abs_unsigned_unsigned! { + u8, abs_u8; + u16, abs_u16; + u32, abs_u32; + u64, abs_u64; +} + +// Sign-preserving abs on unsigned integers (6 functions) +abs_unsigned_unsigned! { + u8 => (abs_u8_u16, u16), (abs_u8_u32, u32), (abs_u8_u64, u64); + u16 => (abs_u16_u32, u32), (abs_u16_u64, u64); + u32 => (abs_u32_u64, u64); +} + +/* 28 omitted abs functions involving lossy conversion + * - u64 -> T != u64 (7) + * - u32 -> T < ?64, != u32 (5) + * - u16 -> T < ?32, != u16 (3) + * - u8 -> i8 (1) + * - i64 -> T < ?64 (6) + * - i32 -> T < ?32 (4) + * - i16 -> T < ?16 (2) + */ + +// !SECTION + +pub fn eval_unary_fallback( + x: In, + _op_hint: &'static str, + op: impl FnOnce(&BigInt) -> BigInt, +) -> Eval +where + BigInt: From, + Out: TryFrom>, +{ + eprintln!("[INFO]: encountered fallback operation `{_op_hint} : ({} -> {})` that may benefit from standalone function", type_name::(), type_name::()); + let big_x = BigInt::from(x); + let big_res = op(&big_x); + match >::try_from(big_res) { + Ok(res) => Eval::Direct(res), + Err(e) => Eval::Indirect(IndirectEval { + value: Rc::new(LazyCell::new(Box::new(move || e.into_original()))), + }), + } +} +// !SECTION + +// SECTION - Pure Casts + +macro_rules! direct_cast { + ($($from:ty => $( ($fname:ident, $to:ty) ),+ $(,)? );+ $(;)? ) => { + $( + $( + pub fn $fname(x: $from) -> Eval<$to> { + Eval::Direct(x as $to) + } + )+ + )+ + } +} + +macro_rules! attempt_cast { + ($($from:ty => $( ($fname:ident, $to:ty) ),+ $(,)? );+ $(;)? ) => { + $( + $( + pub fn $fname(x: $from) -> Eval<$to> { + match <$to as ::num_traits::NumCast>::from(x) { + Some(res) => Eval::Direct(res), + None => Eval::Indirect(IndirectEval { + value: Rc::new(LazyCell::new(Box::new(move || BigInt::from(x)))), + }) + } + } + )+ + )+ + }; +} + +// Identity casts [elided] (8 functions) + +// Natural casts (excluding identity casts) (18 functions) +direct_cast! { + u8 => (cast_u8_u16, u16), (cast_u8_u32, u32), (cast_u8_u64, u64), (cast_u8_i16, i16), (cast_u8_i32, i32), (cast_u8_i64, i64); + u16 => (cast_u16_u32, u32), (cast_u16_u64, u64), (cast_u16_i32, i32), (cast_u16_i64, i64); + u32 => (cast_u32_u64, u64), (cast_u32_i64, i64); + i8 => (cast_i8_i16, i16), (cast_i8_i32, i32), (cast_i8_i64, i64); + i16 => (cast_i16_i32, i32), (cast_i16_i64, i64); + i32 => (cast_i32_i64, i64); +} + +// Fallible casts (38 functions) +attempt_cast! { + u8 => (cast_u8_i8, i8); + u16 => (cast_u16_u8, u8), (cast_u16_i8, i8), (cast_u16_i16, i16); + u32 => (cast_u32_u8, u8), (cast_u32_u16, u16), (cast_u32_i8, i8), (cast_u32_i16, i16), (cast_u32_i32, i32); + u64 => (cast_u64_u8, u8), (cast_u64_u16, u16), (cast_u64_u32, u32), (cast_u64_i8, i8), (cast_u64_i16, i16), (cast_u64_i32, i32), (cast_u64_i64, i64); + i8 => (cast_i8_u8, u8), (cast_i8_u16, u16), (cast_i8_u32, u32), (cast_i8_u64, u64); + i16 => (cast_i16_u8, u8), (cast_i16_u16, u16), (cast_i16_u32, u32), (cast_i16_u64, u64), (cast_i16_i8, i8); + i32 => (cast_i32_u8, u8), (cast_i32_u16, u16), (cast_i32_u32, u32), (cast_i32_u64, u64), (cast_i32_i8, i8), (cast_i32_i16, i16); + i64 => (cast_i64_u8, u8), (cast_i64_u16, u16), (cast_i64_u32, u32), (cast_i64_u64, u64), (cast_i64_i8, i8), (cast_i64_i16, i16), (cast_i64_i32, i32); +} +// !SECTION diff --git a/experiments/analytic-engine/src/gen.rs b/experiments/analytic-engine/src/gen.rs new file mode 100644 index 00000000..fb5b15dc --- /dev/null +++ b/experiments/analytic-engine/src/gen.rs @@ -0,0 +1,443 @@ +use std::borrow::Cow; + +use crate::core::{BasicBinOp, BasicUnaryOp, BinOp, MachineRep, UnaryOp}; +use crate::elaborator::{IntType, PrimInt, Sig1, Sig2, TypedExpr}; +use crate::printer::fragment::Fragment; + +pub(crate) mod ast { + use crate::{core::TypedConst, elaborator::PrimInt}; + + pub type Label = std::borrow::Cow<'static, str>; + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub(crate) enum SType { + RustPrimInt(PrimInt), + } + + #[derive(Clone, Debug)] + pub(crate) enum AstEntity { + Unqualified(Label), + Qualified(Vec