diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5ff07f --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/target + + +# Added by cargo +# +# already existing elements were commented out + +#/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a481889 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,755 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +dependencies = [ + "backtrace", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", + "unicase", + "unicode-width", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "compact_str" +version = "0.8.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a2dc81369dde6d31456eedbb4fd3d320f0b9713573dfe06e569e2bce7607f2" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", + "serde", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", + "rand", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", + "rand", +] + +[[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-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "q" +version = "1.0.0" +dependencies = [ + "anyhow", + "clap", + "clap_derive", + "compact_str", + "hashbrown", + "num", + "rand", + "serde", + "serde_json", + "smallvec", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "log", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..470ae80 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,76 @@ +cargo-features = ["edition2024"] + +[package] +name = "q" +version = "1.0.0" +edition = "2024" +links = "gmp" + +[dependencies] +anyhow = { version = "1.0.86", features = ["backtrace"] } +clap = { version = "4.5.4", features = ["derive", "unicode", "wrap_help", "env", "string", "unstable-v5"] } +clap_derive = { version = "4.5.4", features = ["unstable-v5"] } +compact_str = { version = "0.8.0-beta", features = ["serde"] } +hashbrown = { version = "0.14.5", features = ["serde"] } +num = { version = "0.4.3", features = ["rand"] } +rand = { version = "0.8.5", features = ["log", "nightly"] } +serde = { version = "1.0.202", features = ["derive"] } +serde_json = { version = "1.0.117", features = ["float_roundtrip"] } +smallvec = { version = "1.13.2", features = ["const_new", "may_dangle", "specialization", "union"] } + +[lints.rust] +internal_features = { level = "allow", priority = 1 } +non_snake_case = { level = "allow", priority = 1 } + +[lints.clippy] +pedantic = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } +absolute_paths = { level = "allow", priority = 1 } +arithmetic_side_effects = { level = "allow", priority = 1 } +as_conversions = { level = "allow", priority = 1 } +cast_lossless = { level = "allow", priority = 1 } # u32 -> u64 +cast_possible_truncation = { level = "allow", priority = 1 } # u64 -> u32 +cast_possible_wrap = { level = "allow", priority = 1 } # u32 -> i32 +cast_sign_loss = { level = "allow", priority = 1 } # i32 -> u32 +default_numeric_fallback = { level = "allow", priority = 1 } +option_if_let_else = { level = "allow", priority = 1 } +future_not_send = { level = "allow", priority = 1 } +host_endian_bytes = { level = "allow", priority = 1 } +implicit_return = { level = "allow", priority = 1 } +indexing_slicing = { level = "allow", priority = 1 } +inline_always = { level = "allow", priority = 1 } +integer_division = { level = "allow", priority = 1 } +integer_division_remainder_used = { level = "allow", priority = 1 } +many_single_char_names = { level = "allow", priority = 1 } +min_ident_chars = { level = "allow", priority = 1 } +missing_assert_message = { level = "allow", priority = 1 } +missing_docs_in_private_items = { level = "allow", priority = 1 } +missing_errors_doc = { level = "allow", priority = 1 } +missing_panics_doc = { level = "allow", priority = 1 } +missing_trait_methods = { level = "allow", priority = 1 } +module_name_repetitions = { level = "allow", priority = 1 } +multiple_unsafe_ops_per_block = { level = "allow", priority = 1 } +needless_pass_by_value = { level = "allow", priority = 1 } +non_ascii_literal = { level = "allow", priority = 1 } +pattern_type_mismatch = { level = "allow", priority = 1 } +pub_use = { level = "allow", priority = 1 } +question_mark_used = { level = "allow", priority = 1 } +ref_patterns = { level = "allow", priority = 1 } +self_named_module_files = { level = "allow", priority = 1 } +separated_literal_suffix = { level = "allow", priority = 1 } +shadow_reuse = { level = "allow", priority = 1 } +shadow_same = { level = "allow", priority = 1 } +shadow_unrelated = { level = "allow", priority = 1 } +similar_names = { level = "allow", priority = 1 } +single_call_fn = { level = "allow", priority = 1 } +single_char_lifetime_names = { level = "allow", priority = 1 } +std_instead_of_alloc = { level = "allow", priority = 1 } +std_instead_of_core = { level = "allow", priority = 1 } +string_add = { level = "allow", priority = 1 } +string_slice = { level = "allow", priority = 1 } +unseparated_literal_suffix = { level = "allow", priority = 1 } +wildcard_enum_match_arm = { level = "allow", priority = 1 } + +[[bin]] +name = "q" +path = "src/main.rs" diff --git a/apply_patch.py b/apply_patch.py new file mode 100755 index 0000000..7b10b7a --- /dev/null +++ b/apply_patch.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 + +import hashlib +from argparse import ArgumentParser +from enum import Enum +from pathlib import Path +from shutil import rmtree +from subprocess import run + +def parse_args(): + parser = ArgumentParser() + parser.add_argument('--cargo-path', help='Cargo path of your system (default: ~/.cargo)', type=Path, default=Path.home() / '.cargo') + parser.add_argument('--rustup-path', help='Rustup path of your system (default: ~/.rustup)', type=Path, default=Path.home() / '.rustup') + return parser.parse_args() + +STD = ['core', 'alloc', 'std'] + +class PatchStatus(Enum): + CLEAN = 0, + PATCHED = 1, + BROKEN = 2, + DISCARD = 3, + +def patch_inner(patch, path, is_std=False): + assert patch.is_file() + assert path.is_dir() + + hunks = [] + with open(patch) as f: + for line in f.readlines(): + if line.startswith('index '): + sH, tH = line.split()[1].split('..') + elif line.startswith('--- '): + sF = line[6:].rstrip() + elif line.startswith('+++ '): + tF = line[6:].rstrip() + hunks.append((sF, tF, sH, tH)) + state = None + + def check_hunk(fn, ha): + try: + with open(path / fn) as f: + ct = f.read() + except FileNotFoundError: + return ha.count('0') == len(ha) + h = hashlib.sha1() + ct = ct.encode() + h.update(f'blob {len(ct)}'.encode()) + h.update(b'\0') + h.update(ct) + return h.hexdigest().startswith(ha) + + for sF, tF, sH, tH in hunks: + match state: + case None: + match check_hunk(sF, sH), check_hunk(tF, tH): + case True, False: + state = PatchStatus.CLEAN + case False, True: + state = PatchStatus.PATCHED + case False, False: + state = PatchStatus.BROKEN + break + case PatchStatus.CLEAN: + if not check_hunk(sF, sH): + state = PatchStatus.BROKEN + break + case PatchStatus.PATCHED: + if not check_hunk(tF, tH): + state = PatchStatus.BROKEN + break + case PatchStatus.BROKEN: + break + + # remove std is not supported + if state == PatchStatus.BROKEN: + if is_std: + state = PatchStatus.DISCARD + else: + rmtree(path) + + return patch, path, PatchStatus.PATCHED if state is None else state + +def patch_std(identifier, patch, stdlib): + return ( + f'\x1b[33m======== Applying \x1b[1;35m{identifier}\x1b[33m ========\x1b[0m', + patch_inner(patch, stdlib / identifier, True), + ) + +def patch_cargo(identifier, patch, crates_io): + return ( + f'\x1b[36m======== Applying \x1b[1;35m{identifier}\x1b[36m ========\x1b[0m', + patch_inner(patch, crates_io / identifier), + ) + +def patch_git(identifier, patch, cargo_git): + name, version = identifier.rsplit('-', 1) + for d in cargo_git: + if d.name.startswith(name): + return ( + f'\x1b[32m======== Applying \x1b[1;35m{identifier}\x1b[32m ========\x1b[0m', + patch_inner(patch, d / version), + ) + raise FileNotFoundError(f'Cannot find {identifier}') + +def main(): + args = parse_args() + assert args.cargo_path.is_dir() + assert args.rustup_path.is_dir() + + workspace = Path(__file__).parent + assert workspace.is_dir() + + patches = workspace / 'patches' + assert patches.is_dir() + + toolchain = run(['rustup', 'default'], capture_output=True, cwd=workspace) \ + .stdout \ + .split(None, 1)[0] \ + .decode() + print(f'Use toolchain: \x1b[1;36m{toolchain}\x1b[0m\n') + + stdlib = args.rustup_path / 'toolchains' / toolchain / 'lib/rustlib/src/rust/library' + assert stdlib.is_dir() + + crates_io = args.cargo_path / 'registry/src/index.crates.io-6f17d22bba15001f' + assert crates_io.is_dir() + + cargo_git_dir = args.cargo_path / 'git/checkouts' + cargo_git = [] + try: + cargo_git = [d for d in cargo_git_dir.iterdir() if d.is_dir()] + except: + pass + + responses = [] + need_fetch = False + for patch in patches.iterdir(): + name = patch.name + if not name.endswith('.patch'): + continue + name = name[:-6] + if name in STD: + response = patch_std(name, patch, stdlib) + elif (crates_io / name).is_dir(): + response = patch_cargo(name, patch, crates_io) + else: + response = patch_git(name, patch, cargo_git) + if response: + responses.append(response) + if response[1][2] == PatchStatus.BROKEN: + need_fetch = True + + if need_fetch: + run(['cargo', 'fetch'], cwd=workspace) + + for (prompt, (patch, path, status)) in responses: + print(prompt) + match status: + case PatchStatus.PATCHED: + print('\x1b[1;32m√\x1b[0m') + case PatchStatus.DISCARD: + print(f'std package {path.name} is broken, discarded') + case _: + run(['git', '-C', str(path), 'apply', '--reject', str(patch)]) + print() + +if __name__ == '__main__': + main() diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..dbb8095 --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo::rustc-link-search=native=/opt/homebrew/Cellar/gmp/6.3.0/lib"); + println!("cargo::rustc-link-lib=gmp"); +} diff --git a/patches/compact_str-0.8.0-beta.patch b/patches/compact_str-0.8.0-beta.patch new file mode 100644 index 0000000..9ea8aec --- /dev/null +++ b/patches/compact_str-0.8.0-beta.patch @@ -0,0 +1,1995 @@ +diff --git a/src/cow.rs b/src/cow.rs +new file mode 100644 +index 0000000..f3855d9 +--- /dev/null ++++ b/src/cow.rs +@@ -0,0 +1,1509 @@ ++use alloc::borrow::Cow; ++use alloc::boxed::Box; ++use alloc::fmt; ++use alloc::string::String; ++use core::borrow::{ ++ Borrow, ++ BorrowMut, ++}; ++use core::cmp::Ordering; ++use core::hash::{ ++ Hash, ++ Hasher, ++}; ++use core::marker::PhantomData; ++use core::mem; ++use core::ops::{ ++ Add, ++ AddAssign, ++ Deref, ++ DerefMut, ++ RangeBounds, ++}; ++use core::str::{ ++ FromStr, ++ Utf8Error, ++}; ++ ++use crate::repr::Repr; ++use crate::{ ++ CompactString, ++ Drain, ++ Utf16Error, ++}; ++ ++/// A [`CompactCowStr`] is a compact string type ++/// that can be used as [`Cow`] for [`CompactString`]. ++/// ++/// It can own a string as [`CompactString`] keeping the value on heap ++/// or inline or static reference, or can borrow a str keeping the reference. ++#[repr(transparent)] ++pub struct CompactCowStr<'a>(Repr, PhantomData<&'a ()>); ++ ++static_assertions::assert_eq_size!(CompactString, CompactCowStr); ++static_assertions::assert_eq_align!(CompactString, CompactCowStr); ++ ++impl<'a> CompactCowStr<'a> { ++ #[inline] ++ const fn new_raw(repr: Repr) -> Self { ++ CompactCowStr(repr, PhantomData) ++ } ++ ++ /// Creates a new [`CompactCowStr`] from any type that implements `AsRef`. ++ /// If the string is short enough, then it will be inlined on the stack! ++ /// Otherwise it will be stored as reference. ++ /// ++ /// In a `static` or `const` context you can use the method [`CompactCowStr::const_new()`]. ++ /// ++ /// # Examples ++ /// ++ /// ### Inlined ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// // We can inline strings up to 12 characters long on 32-bit architectures... ++ /// #[cfg(target_pointer_width = "32")] ++ /// let s = "i'm 12 chars"; ++ /// // ...and up to 24 characters on 64-bit architectures! ++ /// #[cfg(target_pointer_width = "64")] ++ /// let s = "i am 24 characters long!"; ++ /// ++ /// let compact = CompactCowStr::new(&s); ++ /// ++ /// assert_eq!(compact, s); ++ /// // we are not allocated on the heap! ++ /// assert!(!compact.is_heap_allocated()); ++ /// ``` ++ /// ++ /// ### Reference ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// // For longer strings though, we preserve the reference. ++ /// let long = "I am a longer string that will be preserved as a reference"; ++ /// let compact = CompactCowStr::new(long); ++ /// ++ /// assert_eq!(compact, long); ++ /// // we keep the same reference! ++ /// assert_eq!(compact.as_ptr(), long.as_ptr()); ++ /// ``` ++ #[inline] ++ #[track_caller] ++ pub fn new>(text: T) -> Self { ++ Self::new_raw(Repr::new_ref(text.as_ref())) ++ } ++ ++ /// Creates a new inline [`CompactCowStr`] from `&'static str` at compile time. ++ /// Complexity: O(1). As an optimization, short strings get inlined. ++ /// ++ /// In a dynamic context you can use the method [`CompactCowStr::new()`]. ++ /// ++ /// # Examples ++ /// ``` ++ /// use compact_str::CompactCowStr; ++ /// ++ /// const DEFAULT_NAME: CompactCowStr = CompactCowStr::const_new("untitled"); ++ /// ``` ++ #[inline] ++ pub const fn const_new(text: &'static str) -> Self { ++ CompactCowStr::new_raw(Repr::new_inline(text)) ++ } ++ ++ /// Get back the `&'a str` if it was stored as borrowed reference. ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// // For longer strings though, we preserve the reference. ++ /// let long = "I am a longer string that will be preserved as a reference"; ++ /// let compact = CompactCowStr::new(long); ++ /// ++ /// assert_eq!(compact, long); ++ /// // we keep the same reference! ++ /// assert_eq!(compact.as_ref_str().unwrap().as_ptr(), long.as_ptr()); ++ /// ++ /// const DEFAULT_NAME: CompactCowStr = ++ /// CompactCowStr::const_new("That is not dead which can eternal lie."); ++ /// assert_eq!( ++ /// DEFAULT_NAME.as_ref_str().unwrap(), ++ /// "That is not dead which can eternal lie.", ++ /// ); ++ /// ``` ++ #[inline] ++ #[rustversion::attr(since(1.64), const)] ++ pub fn as_ref_str(&'a self) -> Option<&'a str> { ++ self.0.as_ref_str() ++ } ++ ++ /// Get back the `&'static str` constructed by [`CompactCowStr::const_new`]. ++ /// ++ /// If the string was short enough that it could be inlined, then it was inline, and ++ /// this method will return `None`. ++ /// ++ /// # Examples ++ /// ``` ++ /// use compact_str::CompactCowStr; ++ /// ++ /// const DEFAULT_NAME: CompactCowStr = ++ /// CompactCowStr::const_new("That is not dead which can eternal lie."); ++ /// assert_eq!( ++ /// DEFAULT_NAME.as_static_str().unwrap(), ++ /// "That is not dead which can eternal lie.", ++ /// ); ++ /// ``` ++ #[inline] ++ #[rustversion::attr(since(1.64), const)] ++ pub fn as_static_str(&self) -> Option<&'static str> { ++ self.0.as_static_str() ++ } ++ ++ /// Creates a new empty [`CompactCowStr`] with the capacity to fit at least `capacity` bytes. ++ /// This will ++ /// ++ /// This function behaves similarly to the [`CompactCowStr::with_capacity`] function. ++ /// ++ /// A `CompactCowStr` will inline strings on the stack, if they're small enough. Specifically, ++ /// if the string has a length less than or equal to `std::mem::size_of::` bytes ++ /// then it will be inlined. This also means that `CompactCowStr`s have a minimum capacity ++ /// of `std::mem::size_of::`. ++ /// ++ /// # Panics ++ /// ++ /// This method panics if the system is out-of-memory. ++ /// Use [`CompactCowStr::try_with_capacity()`] if you want to handle such a problem manually. ++ /// ++ /// # Examples ++ /// ++ /// ### "zero" Capacity ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// // Creating a CompactCowStr with a capacity of 0 will create ++ /// // one with capacity of std::mem::size_of::(); ++ /// let empty = CompactCowStr::with_capacity(0); ++ /// let min_size = std::mem::size_of::(); ++ /// ++ /// assert_eq!(empty.capacity(), min_size); ++ /// assert_ne!(0, min_size); ++ /// assert!(!empty.is_heap_allocated()); ++ /// ``` ++ /// ++ /// ### Max Inline Size ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// // Creating a CompactCowStr with a capacity of std::mem::size_of::() ++ /// // will not heap allocate. ++ /// let str_size = std::mem::size_of::(); ++ /// let empty = CompactCowStr::with_capacity(str_size); ++ /// ++ /// assert_eq!(empty.capacity(), str_size); ++ /// assert!(!empty.is_heap_allocated()); ++ /// ``` ++ /// ++ /// ### Heap Allocating ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// // If you create a `CompactCowStr` with a capacity greater than ++ /// // `std::mem::size_of::`, it will heap allocated. For heap ++ /// // allocated strings we have a minimum capacity ++ /// ++ /// const MIN_HEAP_CAPACITY: usize = std::mem::size_of::() * 4; ++ /// ++ /// let heap_size = std::mem::size_of::() + 1; ++ /// let empty = CompactCowStr::with_capacity(heap_size); ++ /// ++ /// assert_eq!(empty.capacity(), MIN_HEAP_CAPACITY); ++ /// assert!(empty.is_heap_allocated()); ++ /// ``` ++ #[inline] ++ #[track_caller] ++ pub fn with_capacity(capacity: usize) -> Self { ++ CompactString::with_capacity(capacity).into() ++ } ++ ++ /// Convert a slice of bytes into a [`CompactCowStr`]. ++ /// ++ /// A [`CompactCowStr`] is a contiguous collection of bytes (`u8`s) that is valid [`UTF-8`](https://en.wikipedia.org/wiki/UTF-8). ++ /// This method converts from an arbitrary contiguous collection of bytes into a ++ /// [`CompactCowStr`], failing if the provided bytes are not `UTF-8`. ++ /// ++ /// # Examples ++ /// ### Valid UTF-8 ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let bytes = vec![240, 159, 166, 128, 240, 159, 146, 175]; ++ /// let compact = CompactCowStr::from_utf8(bytes).expect("valid UTF-8"); ++ /// ++ /// assert_eq!(compact, "🦀💯"); ++ /// ``` ++ /// ++ /// ### Invalid UTF-8 ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let bytes = vec![255, 255, 255]; ++ /// let result = CompactCowStr::from_utf8(bytes); ++ /// ++ /// assert!(result.is_err()); ++ /// ``` ++ #[inline] ++ pub fn from_utf8>(buf: B) -> Result { ++ Repr::from_utf8_ref(buf).map(CompactCowStr::new_raw) ++ } ++ ++ /// Converts a vector of bytes to a [`CompactString`] without checking that the string contains ++ /// valid UTF-8. ++ /// ++ /// See the safe version, [`CompactCowStr::from_utf8`], for more details. ++ /// ++ /// # Safety ++ /// ++ /// This function is unsafe because it does not check that the bytes passed to it are valid ++ /// UTF-8. If this constraint is violated, it may cause memory unsafety issues with future users ++ /// of the [`CompactCowStr`], as the rest of the standard library assumes that ++ /// [`CompactCowStr`]s are valid UTF-8. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// // some bytes, in a vector ++ /// let sparkle_heart = vec![240, 159, 146, 150]; ++ /// ++ /// let sparkle_heart = unsafe { ++ /// CompactCowStr::from_utf8_unchecked(sparkle_heart) ++ /// }; ++ /// ++ /// assert_eq!("💖", sparkle_heart); ++ /// ``` ++ #[inline] ++ #[must_use] ++ #[track_caller] ++ pub unsafe fn from_utf8_unchecked>(buf: B) -> Self { ++ CompactCowStr::new_raw(Repr::from_utf8_unchecked_ref(buf)) ++ } ++ ++ /// Decode a [`UTF-16`](https://en.wikipedia.org/wiki/UTF-16) slice of bytes into a ++ /// [`CompactCowStr`], returning an [`Err`] if the slice contains any invalid data. ++ /// ++ /// ++ /// # Examples ++ /// ### Valid UTF-16 ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let buf: &[u16] = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; ++ /// let compact = CompactCowStr::from_utf16(buf).unwrap(); ++ /// ++ /// assert_eq!(compact, "𝄞music"); ++ /// ``` ++ /// ++ /// ### Invalid UTF-16 ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let buf: &[u16] = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; ++ /// let res = CompactCowStr::from_utf16(buf); ++ /// ++ /// assert!(res.is_err()); ++ /// ``` ++ #[inline] ++ pub fn from_utf16>(buf: B) -> Result { ++ CompactString::from_utf16(buf).map(Into::into) ++ } ++ ++ /// Decode a UTF-16–encoded slice `v` into a `CompactString`, replacing invalid data with ++ /// the replacement character (`U+FFFD`), �. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// // 𝄞music ++ /// let v = &[0xD834, 0xDD1E, 0x006d, 0x0075, ++ /// 0x0073, 0xDD1E, 0x0069, 0x0063, ++ /// 0xD834]; ++ /// ++ /// assert_eq!(CompactCowStr::from("𝄞mus\u{FFFD}ic\u{FFFD}"), ++ /// CompactCowStr::from_utf16_lossy(v)); ++ /// ``` ++ #[inline] ++ pub fn from_utf16_lossy>(buf: B) -> Self { ++ CompactString::from_utf16_lossy(buf).into() ++ } ++ ++ /// Returns the length of the [`CompactCowStr`] in `bytes`, not [`char`]s or graphemes. ++ /// ++ /// When using `UTF-8` encoding (which all strings in Rust do) a single character will be 1 to 4 ++ /// bytes long, therefore the return value of this method might not be what a human considers ++ /// the length of the string. ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let ascii = CompactCowStr::new("hello world"); ++ /// assert_eq!(ascii.len(), 11); ++ /// ++ /// let emoji = CompactCowStr::new("👱"); ++ /// assert_eq!(emoji.len(), 4); ++ /// ``` ++ #[inline] ++ pub fn len(&self) -> usize { ++ self.0.len() ++ } ++ ++ /// Returns `true` if the [`CompactString`] has a length of 0, `false` otherwise ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut msg = CompactCowStr::new(""); ++ /// assert!(msg.is_empty()); ++ /// ++ /// // add some characters ++ /// msg.push_str("hello reader!"); ++ /// assert!(!msg.is_empty()); ++ /// ``` ++ #[inline] ++ pub fn is_empty(&self) -> bool { ++ self.0.len() == 0 ++ } ++ ++ /// Returns the capacity of the [`CompactCowStr`], in bytes. ++ /// ++ /// # Note ++ /// * A `CompactCowStr` will always have a capacity of at least `std::mem::size_of::()` ++ /// ++ /// # Examples ++ /// ### Minimum Size ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let min_size = std::mem::size_of::(); ++ /// let compact = CompactCowStr::new(""); ++ /// ++ /// assert!(compact.capacity() >= min_size); ++ /// ``` ++ /// ++ /// ### Heap Allocated ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let compact = CompactCowStr::with_capacity(128); ++ /// assert_eq!(compact.capacity(), 128); ++ /// ``` ++ #[inline] ++ pub fn capacity(&self) -> usize { ++ self.0.capacity() ++ } ++ ++ /// Ensures that this [`CompactCowStr`]'s capacity is at least `additional` bytes longer than ++ /// its length. The capacity may be increased by more than `additional` bytes if it chooses, ++ /// to prevent frequent reallocations. ++ /// ++ /// # Note ++ /// * A `CompactCowStr` will always have at least a capacity of `std::mem::size_of::()` ++ /// * Reserving additional bytes may cause the `CompactCowStr` to become heap allocated ++ /// ++ /// # Panics ++ /// This method panics if the new capacity overflows `usize` or if the system is out-of-memory. ++ /// Use [`CompactCowStr::try_reserve()`] if you want to handle such a problem manually. ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// ++ /// const WORD: usize = std::mem::size_of::(); ++ /// let mut compact = CompactCowStr::default(); ++ /// assert!(compact.capacity() >= (WORD * 3) - 1); ++ /// ++ /// compact.reserve(200); ++ /// assert!(compact.is_heap_allocated()); ++ /// assert!(compact.capacity() >= 200); ++ /// ``` ++ #[inline] ++ #[track_caller] ++ pub fn reserve(&mut self, additional: usize) { ++ self.reserve(additional) ++ } ++ ++ /// Returns a string slice containing the entire [`CompactCowStr`]. ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let s = CompactCowStr::new("hello"); ++ /// ++ /// assert_eq!(s.as_str(), "hello"); ++ /// ``` ++ #[inline] ++ pub fn as_str(&self) -> &str { ++ self.0.as_str() ++ } ++ ++ /// Returns a mutable string slice containing the entire [`CompactCowStr`]. ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("hello"); ++ /// s.as_mut_str().make_ascii_uppercase(); ++ /// ++ /// assert_eq!(s.as_str(), "HELLO"); ++ /// ``` ++ #[inline] ++ pub fn as_mut_str(&mut self) -> &mut str { ++ self.to_mut().as_mut_str() ++ } ++ ++ /// Returns a byte slice of the [`CompactCowStr`]'s contents. ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let s = CompactCowStr::new("hello"); ++ /// ++ /// assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes()); ++ /// ``` ++ #[inline] ++ pub fn as_bytes(&self) -> &[u8] { ++ self.to_ref().as_bytes() ++ } ++ ++ // TODO: Implement a `try_as_mut_slice(...)` that will fail if it results in cloning? ++ // ++ /// Provides a mutable reference to the underlying buffer of bytes. ++ /// ++ /// # Safety ++ /// * All Rust strings, including `CompactCowStr`, must be valid UTF-8. The caller must ++ /// guarantee ++ /// that any modifications made to the underlying buffer are valid UTF-8. ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("hello"); ++ /// ++ /// let slice = unsafe { s.as_mut_bytes() }; ++ /// // copy bytes into our string ++ /// slice[5..11].copy_from_slice(" world".as_bytes()); ++ /// // set the len of the string ++ /// unsafe { s.set_len(11) }; ++ /// ++ /// assert_eq!(s, "hello world"); ++ /// ``` ++ #[inline] ++ pub unsafe fn as_mut_bytes(&mut self) -> &mut [u8] { ++ self.to_mut().as_mut_bytes() ++ } ++ ++ /// Appends the given [`char`] to the end of this [`CompactCowStr`]. ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("foo"); ++ /// ++ /// s.push('b'); ++ /// s.push('a'); ++ /// s.push('r'); ++ /// ++ /// assert_eq!("foobar", s); ++ /// ``` ++ pub fn push(&mut self, ch: char) { ++ self.push_str(ch.encode_utf8(&mut [0; 4])); ++ } ++ ++ /// Removes the last character from the [`CompactCowStr`] and returns it. ++ /// Returns `None` if this [`CompactCowStr`] is empty. ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("abc"); ++ /// ++ /// assert_eq!(s.pop(), Some('c')); ++ /// assert_eq!(s.pop(), Some('b')); ++ /// assert_eq!(s.pop(), Some('a')); ++ /// ++ /// assert_eq!(s.pop(), None); ++ /// ``` ++ #[inline] ++ pub fn pop(&mut self) -> Option { ++ self.to_mut().pop() ++ } ++ ++ /// Appends a given string slice onto the end of this [`CompactCowStr`] ++ /// ++ /// # Examples ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("abc"); ++ /// ++ /// s.push_str("123"); ++ /// ++ /// assert_eq!("abc123", s); ++ /// ``` ++ #[inline] ++ pub fn push_str(&mut self, s: &str) { ++ self.to_mut().push_str(s) ++ } ++ ++ /// Removes a [`char`] from this [`CompactCowStr`] at a byte position and returns it. ++ /// ++ /// This is an *O*(*n*) operation, as it requires copying every element in the ++ /// buffer. ++ /// ++ /// # Panics ++ /// ++ /// Panics if `idx` is larger than or equal to the [`CompactCowStr`]'s length, ++ /// or if it does not lie on a [`char`] boundary. ++ /// ++ /// # Examples ++ /// ++ /// ### Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut c = CompactCowStr::from("hello world"); ++ /// ++ /// assert_eq!(c.remove(0), 'h'); ++ /// assert_eq!(c, "ello world"); ++ /// ++ /// assert_eq!(c.remove(5), 'w'); ++ /// assert_eq!(c, "ello orld"); ++ /// ``` ++ /// ++ /// ### Past total length: ++ /// ++ /// ```should_panic ++ /// # use compact_str::CompactCowStr; ++ /// let mut c = CompactCowStr::from("hello there!"); ++ /// c.remove(100); ++ /// ``` ++ /// ++ /// ### Not on char boundary: ++ /// ++ /// ```should_panic ++ /// # use compact_str::CompactCowStr; ++ /// let mut c = CompactCowStr::from("🦄"); ++ /// c.remove(1); ++ /// ``` ++ #[inline] ++ pub fn remove(&mut self, idx: usize) -> char { ++ self.to_mut().remove(idx) ++ } ++ ++ /// Forces the length of the [`CompactCowStr`] to `new_len`. ++ /// ++ /// This is a low-level operation that maintains none of the normal invariants for ++ /// `CompactCowStr`. If you want to modify the `CompactCowStr` you should use methods like ++ /// `push`, `push_str` or `pop`. ++ /// ++ /// This doesn't clone and mark as owned. ++ /// ++ /// # Safety ++ /// * `new_len` must be less than or equal to `capacity()` ++ /// * The elements at `old_len..new_len` must be initialized ++ #[inline] ++ pub unsafe fn set_len(&mut self, new_len: usize) { ++ self.0.set_len(new_len) ++ } ++ ++ /// Returns whether or not the [`CompactCowStr`] is heap allocated. ++ /// ++ /// # Examples ++ /// ### Inlined ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let hello = CompactCowStr::new("hello world"); ++ /// ++ /// assert!(!hello.is_heap_allocated()); ++ /// ``` ++ /// ++ /// ### Reference ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let msg = CompactCowStr::new("this message will self destruct in 5, 4, 3, 2, 1 💥"); ++ /// ++ /// assert!(!msg.is_heap_allocated()); ++ /// ``` ++ /// ++ /// ### Heap Allocated ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut msg = CompactCowStr::new("this message will self destruct in 5, 4, 3, 2, 1 💥"); ++ /// msg.replace_range(0..1, "T"); ++ /// assert!(msg.is_heap_allocated()); ++ /// ``` ++ #[inline] ++ pub fn is_heap_allocated(&self) -> bool { ++ self.0.is_heap_allocated() ++ } ++ ++ /// Returns whether or not the [`CompactCowStr`] is borrowed. ++ /// This means that resource is not owned, and mutating this will cause clone. ++ /// ++ /// # Examples ++ /// ### Inlined ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let hello = CompactCowStr::new("hello world"); ++ /// ++ /// assert!(!hello.is_borrowed()); ++ /// ``` ++ /// ++ /// ### Static ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let msg = CompactCowStr::const_new("this message will self destruct in 5, 4, 3, 2, 1 💥"); ++ /// ++ /// assert!(msg.is_borrowed()); ++ /// ``` ++ /// ++ /// ### Reference ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let msg = CompactCowStr::new("this message will self destruct in 5, 4, 3, 2, 1 💥"); ++ /// ++ /// assert!(msg.is_borrowed()); ++ /// ``` ++ /// ++ /// ### Heap Allocated ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut msg = CompactCowStr::new("this message will self destruct in 5, 4, 3, 2, 1 💥"); ++ /// msg.to_mut(); ++ /// assert!(!msg.is_borrowed()); ++ /// ``` ++ #[inline] ++ pub fn is_borrowed(&self) -> bool { ++ self.0.is_ref_str() ++ } ++ ++ /// Removes the specified range in the [`CompactCowStr`], ++ /// and replaces it with the given string. ++ /// The given string doesn't need to be the same length as the range. ++ /// ++ /// # Panics ++ /// ++ /// Panics if the starting point or end point do not lie on a [`char`] ++ /// boundary, or if they're out of bounds. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("Hello, world!"); ++ /// ++ /// s.replace_range(7..12, "WORLD"); ++ /// assert_eq!(s, "Hello, WORLD!"); ++ /// ++ /// s.replace_range(7..=11, "you"); ++ /// assert_eq!(s, "Hello, you!"); ++ /// ++ /// s.replace_range(5.., "! Is it me you're looking for?"); ++ /// assert_eq!(s, "Hello! Is it me you're looking for?"); ++ /// ``` ++ #[inline] ++ pub fn replace_range(&mut self, range: impl RangeBounds, replace_with: &str) { ++ self.to_mut().replace_range(range, replace_with) ++ } ++ ++ /// Creates a new [`CompactCowStr`] by repeating a string `n` times. ++ /// ++ /// # Panics ++ /// ++ /// This function will panic if the capacity would overflow. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// use compact_str::CompactCowStr; ++ /// assert_eq!(CompactCowStr::new("abc").repeat(4), CompactCowStr::new("abcabcabcabc")); ++ /// ``` ++ /// ++ /// A panic upon overflow: ++ /// ++ /// ```should_panic ++ /// use compact_str::CompactCowStr; ++ /// ++ /// // this will panic at runtime ++ /// let huge = CompactCowStr::new("0123456789abcdef").repeat(usize::MAX); ++ /// ``` ++ #[must_use] ++ pub fn repeat(&self, n: usize) -> Self { ++ self.to_ref().repeat(n).into() ++ } ++ ++ /// Truncate the [`CompactCowStr`] to a shorter length. ++ /// ++ /// If the length of the [`CompactCowStr`] is less or equal to `new_len`, the call is a no-op. ++ /// ++ /// Calling this function does not change the capacity of the [`CompactCowStr`]. ++ /// ++ /// # Panics ++ /// ++ /// Panics if the new end of the string does not lie on a [`char`] boundary. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("Hello, world!"); ++ /// s.truncate(5); ++ /// assert_eq!(s, "Hello"); ++ /// ``` ++ pub fn truncate(&mut self, new_len: usize) { ++ self.to_mut().truncate(new_len) ++ } ++ ++ /// Converts a [`CompactCowStr`] to a raw pointer. ++ #[inline] ++ pub fn as_ptr(&self) -> *const u8 { ++ self.to_ref().as_ptr() ++ } ++ ++ /// Converts a mutable [`CompactCowStr`] to a raw pointer. ++ #[inline] ++ pub fn as_mut_ptr(&mut self) -> *mut u8 { ++ self.to_mut().as_mut_ptr() ++ } ++ ++ /// Insert string character at an index. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("Hello!"); ++ /// s.insert_str(5, ", world"); ++ /// assert_eq!(s, "Hello, world!"); ++ /// ``` ++ pub fn insert_str(&mut self, idx: usize, string: &str) { ++ self.to_mut().insert_str(idx, string) ++ } ++ ++ /// Insert a character at an index. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("Hello world!"); ++ /// s.insert(5, ','); ++ /// assert_eq!(s, "Hello, world!"); ++ /// ``` ++ pub fn insert(&mut self, idx: usize, ch: char) { ++ self.insert_str(idx, ch.encode_utf8(&mut [0; 4])); ++ } ++ ++ /// Reduces the length of the [`CompactCowStr`] to zero. ++ /// ++ /// Calling this function does not change the capacity of the [`CompactCowStr`]. ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("Rust is the most loved language on Stackoverflow!"); ++ /// assert_eq!(s.capacity(), 49); ++ /// ++ /// s.clear(); ++ /// ++ /// assert_eq!(s, ""); ++ /// assert_eq!(s.capacity(), 49); ++ /// ``` ++ pub fn clear(&mut self) { ++ self.to_mut().clear() ++ } ++ ++ /// Split the [`CompactCowStr`] into at the given byte index. ++ /// ++ /// Calling this function does not change the capacity of the [`CompactCowStr`], unless the ++ /// [`CompactCowStr`] is backed by a `&str`. ++ /// ++ /// # Panics ++ /// ++ /// Panics if `at` does not lie on a [`char`] boundary. ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::const_new("Hello, world!"); ++ /// let w = s.split_off(5); ++ /// ++ /// assert_eq!(w, ", world!"); ++ /// assert_eq!(s, "Hello"); ++ /// ``` ++ pub fn split_off(&mut self, at: usize) -> Self { ++ if let Some(s) = self.as_static_str() { ++ let result = Self::const_new(&s[at..]); ++ // SAFETY: the previous line `self[at...]` would have panicked if `at` was invalid ++ unsafe { self.set_len(at) }; ++ result ++ } else { ++ // This will make result as borrowed str. ++ let result = self[at..].into(); ++ // SAFETY: the previous line `self[at...]` would have panicked if `at` was invalid ++ unsafe { self.set_len(at) }; ++ result ++ } ++ } ++ ++ /// Remove a range from the [`CompactCowStr`], and return it as an iterator. ++ /// ++ /// Calling this function does not change the capacity of the [`CompactCowStr`]. ++ /// ++ /// # Panics ++ /// ++ /// Panics if the start or end of the range does not lie on a [`char`] boundary. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::new("Hello, world!"); ++ /// ++ /// let mut d = s.drain(5..12); ++ /// assert_eq!(d.next(), Some(',')); // iterate over the extracted data ++ /// assert_eq!(d.as_str(), " world"); // or get the whole data as &str ++ /// ++ /// // The iterator keeps a reference to `s`, so you have to drop() the iterator, ++ /// // before you can access `s` again. ++ /// drop(d); ++ /// assert_eq!(s, "Hello!"); ++ /// ``` ++ pub fn drain(&mut self, range: impl RangeBounds) -> Drain<'_> { ++ self.to_mut().drain(range) ++ } ++ ++ /// Shrinks the capacity of this [`CompactCowStr`] with a lower bound. ++ /// ++ /// The resulting capactity is never less than the size of 3×[`usize`], ++ /// i.e. the capacity than can be inlined. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::with_capacity(100); ++ /// assert_eq!(s.capacity(), 100); ++ /// ++ /// // if the capacity was already bigger than the argument, the call is a no-op ++ /// s.shrink_to(100); ++ /// assert_eq!(s.capacity(), 100); ++ /// ++ /// s.shrink_to(50); ++ /// assert_eq!(s.capacity(), 50); ++ /// ++ /// // if the string can be inlined, it is ++ /// s.shrink_to(10); ++ /// assert_eq!(s.capacity(), 3 * std::mem::size_of::()); ++ /// ``` ++ #[inline] ++ pub fn shrink_to(&mut self, min_capacity: usize) { ++ if self.is_heap_allocated() { ++ self.to_mut().shrink_to(min_capacity) ++ } ++ } ++ ++ /// Shrinks the capacity of this [`CompactString`] to match its length. ++ /// ++ /// The resulting capactity is never less than the size of 3×[`usize`], ++ /// i.e. the capacity than can be inlined. ++ /// ++ /// This method is effectively the same as calling [`string.shrink_to(0)`]. ++ /// ++ /// # Examples ++ /// ++ /// Basic usage: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::from("This is a string with more than 24 characters."); ++ /// ++ /// s.reserve(100); ++ /// assert!(s.capacity() >= 100); ++ /// ++ /// s.shrink_to_fit(); ++ /// assert_eq!(s.len(), s.capacity()); ++ /// ``` ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::from("short string"); ++ /// ++ /// s.reserve(100); ++ /// assert!(s.capacity() >= 100); ++ /// ++ /// s.shrink_to_fit(); ++ /// assert_eq!(s.capacity(), 3 * std::mem::size_of::()); ++ /// ``` ++ #[inline] ++ pub fn shrink_to_fit(&mut self) { ++ self.shrink_to(0) ++ } ++ ++ /// Retains only the characters specified by the predicate. ++ /// ++ /// The method iterates over the characters in the string and calls the `predicate`. ++ /// ++ /// If the `predicate` returns `false`, then the character gets removed. ++ /// If the `predicate` returns `true`, then the character is kept. ++ /// ++ /// # Examples ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let mut s = CompactCowStr::from("äb𝄞d€"); ++ /// ++ /// let keep = [false, true, true, false, true]; ++ /// let mut iter = keep.iter(); ++ /// s.retain(|_| *iter.next().unwrap()); ++ /// ++ /// assert_eq!(s, "b𝄞€"); ++ /// ``` ++ pub fn retain(&mut self, predicate: impl FnMut(char) -> bool) { ++ self.to_mut().retain(predicate) ++ } ++ ++ /// Decode a bytes slice as UTF-8 string, replacing any illegal codepoints ++ /// ++ /// # Examples ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let chess_knight = b"\xf0\x9f\xa8\x84"; ++ /// ++ /// assert_eq!( ++ /// "🨄", ++ /// CompactCowStr::from_utf8_lossy(chess_knight), ++ /// ); ++ /// ++ /// // For valid UTF-8 slices, this is the same as: ++ /// assert_eq!( ++ /// "🨄", ++ /// CompactCowStr::new(std::str::from_utf8(chess_knight).unwrap()), ++ /// ); ++ /// ``` ++ /// ++ /// Incorrect bytes: ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let broken = b"\xf0\x9f\xc8\x84"; ++ /// ++ /// assert_eq!( ++ /// "�Ȅ", ++ /// CompactCowStr::from_utf8_lossy(broken), ++ /// ); ++ /// ++ /// // For invalid UTF-8 slices, this is an optimized implemented for: ++ /// assert_eq!( ++ /// "�Ȅ", ++ /// CompactCowStr::from(String::from_utf8_lossy(broken)), ++ /// ); ++ /// ``` ++ pub fn from_utf8_lossy(v: &[u8]) -> Self { ++ // fixme: optimize ++ String::from_utf8_lossy(v).into() ++ } ++ ++ /// Convert the [`CompactCowStr`] into a [`String`]. ++ /// ++ /// # Examples ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let s = CompactCowStr::new("Hello world"); ++ /// let s = s.into_string(); ++ /// assert_eq!(s, "Hello world"); ++ /// ``` ++ pub fn into_string(self) -> String { ++ self.0.into_string() ++ } ++ ++ /// Convert a [`String`] into a [`CompactCowStr`] _without inlining_. ++ /// ++ /// Note: You probably don't need to use this method, instead you should use `From` ++ /// which is implemented for [`CompactCowStr`]. ++ /// ++ /// This method exists incase your code is very sensitive to memory allocations. Normally when ++ /// converting a [`String`] to a [`CompactCowStr`] we'll inline short strings onto the stack. ++ /// But this results in [`Drop`]-ing the original [`String`], which causes memory it owned on ++ /// the heap to be deallocated. Instead when using this method, we always reuse the buffer that ++ /// was previously owned by the [`String`], so no trips to the allocator are needed. ++ /// ++ /// # Examples ++ /// ++ /// ### Short Strings ++ /// ``` ++ /// use compact_str::CompactCowStr; ++ /// ++ /// let short = "hello world".to_string(); ++ /// let c_heap = CompactCowStr::from_string_buffer(short); ++ /// ++ /// // using CompactCowStr::from_string_buffer, we'll re-use the String's underlying buffer ++ /// assert!(c_heap.is_heap_allocated()); ++ /// ++ /// // note: when Clone-ing a short heap allocated string, we'll eagerly inline at that point ++ /// let c_inline = c_heap.clone(); ++ /// assert!(!c_inline.is_heap_allocated()); ++ /// ++ /// assert_eq!(c_heap, c_inline); ++ /// ``` ++ /// ++ /// ### Longer Strings ++ /// ``` ++ /// use compact_str::CompactCowStr; ++ /// ++ /// let x = "longer string that will be on the heap".to_string(); ++ /// let c1 = CompactCowStr::from(x); ++ /// ++ /// let y = "longer string that will be on the heap".to_string(); ++ /// let c2 = CompactCowStr::from_string_buffer(y); ++ /// ++ /// // for longer strings, we re-use the underlying String's buffer in both cases ++ /// assert!(c1.is_heap_allocated()); ++ /// assert!(c2.is_heap_allocated()); ++ /// ``` ++ /// ++ /// ### Buffer Re-use ++ /// ``` ++ /// use compact_str::CompactCowStr; ++ /// ++ /// let og = "hello world".to_string(); ++ /// let og_addr = og.as_ptr(); ++ /// ++ /// let mut c = CompactCowStr::from_string_buffer(og); ++ /// let ex_addr = c.as_ptr(); ++ /// ++ /// // When converting to/from String and CompactCowStr with from_string_buffer we always re-use ++ /// // the same underlying allocated memory/buffer ++ /// assert_eq!(og_addr, ex_addr); ++ /// ++ /// let long = "this is a long string that will be on the heap".to_string(); ++ /// let long_addr = long.as_ptr(); ++ /// ++ /// let mut long_c = CompactCowStr::from(long); ++ /// let long_ex_addr = long_c.as_ptr(); ++ /// ++ /// // When converting to/from String and CompactCowStr with From, we'll also re-use the ++ /// // underlying buffer, if the string is long, otherwise when converting to CompactString we ++ /// // eagerly inline ++ /// assert_eq!(long_addr, long_ex_addr); ++ /// ``` ++ #[inline] ++ #[track_caller] ++ pub fn from_string_buffer(s: String) -> Self { ++ CompactString::from_string_buffer(s).into() ++ } ++ ++ #[inline] ++ fn into_compact_string(mut self) -> CompactString { ++ self.0.make_owned(); ++ // SAFETY: make_owned replaces COW flag. ++ unsafe { mem::transmute(self) } ++ } ++ ++ #[inline] ++ fn to_ref(&self) -> &CompactString { ++ // SAFETY: This might leak COW flag to `CompactString` so caller ++ // must make sure not to leak. ++ unsafe { mem::transmute(self) } ++ } ++ ++ /// Acquires a mutable reference to the owned form of the data. ++ /// Clones the data if it is not already owned. ++ /// ++ /// ``` ++ /// # use compact_str::CompactCowStr; ++ /// let original = "This is a string with more than 24 characters."; ++ /// let mut cow_str = CompactCowStr::new(original); ++ /// assert_eq!(cow_str.as_ptr(), original.as_ptr()); ++ /// assert!(cow_str.is_borrowed()); ++ /// assert!(!cow_str.is_heap_allocated()); ++ /// cow_str.to_mut(); ++ /// assert_ne!(cow_str.as_ptr(), original.as_ptr()); ++ /// assert!(!cow_str.is_borrowed()); ++ /// assert!(cow_str.is_heap_allocated()); ++ /// ``` ++ #[inline] ++ pub fn to_mut(&mut self) -> &mut CompactString { ++ self.0.make_owned(); ++ // SAFETY: make_owned replaces COW flag. ++ unsafe { mem::transmute(self) } ++ } ++} ++ ++impl<'a> From for CompactCowStr<'a> { ++ #[inline] ++ fn from(value: CompactString) -> Self { ++ // SAFETY: ++ // * A `HeapBuffer` and `Repr` have the same size ++ // * and all LastUtf8Char is valid for `CompactCowStr` ++ unsafe { mem::transmute(value) } ++ } ++} ++ ++impl<'a> From<&'a CompactString> for CompactCowStr<'a> { ++ #[inline] ++ fn from(value: &'a CompactString) -> Self { ++ if value.is_heap_allocated() { ++ // Create a new cow str as borrowed from source value. ++ Self::new(value.as_str()) ++ } else { ++ // SAFETY: ++ // If the original CompactString is not heap allocated, ++ // we need to preserve whether this repr is stacic or non-static refernce, ++ // or is on the stack, so clone the inner repr. ++ unsafe { CompactCowStr::new_raw(core::ptr::read(&value.0)) } ++ } ++ } ++} ++ ++impl<'a, 'b> From<&'a CompactCowStr<'b>> for CompactCowStr<'a> { ++ #[inline] ++ fn from(value: &'a CompactCowStr<'b>) -> Self { ++ if value.is_heap_allocated() { ++ // Create a new cow str as borrowed from source value. ++ Self::new(value.as_str()) ++ } else { ++ // If the original CompactString is not heap allocated, ++ // we need to preserve whether this repr is stacic or non-static refernce, ++ // or is on the stack, so clone the inner repr. ++ unsafe { CompactCowStr::new_raw(core::ptr::read(&value.0)) } ++ } ++ } ++} ++ ++impl<'a> From> for CompactString { ++ #[inline] ++ fn from(value: CompactCowStr<'a>) -> Self { ++ value.into_compact_string() ++ } ++} ++ ++impl Clone for CompactCowStr<'_> { ++ #[inline] ++ fn clone(&self) -> Self { ++ Self::new_raw(self.0.clone()) ++ } ++ ++ #[inline] ++ fn clone_from(&mut self, source: &Self) { ++ self.0.clone_from(&source.0) ++ } ++} ++ ++impl Default for CompactCowStr<'_> { ++ #[inline] ++ fn default() -> Self { ++ CompactCowStr::new("") ++ } ++} ++ ++impl Deref for CompactCowStr<'_> { ++ type Target = str; ++ ++ #[inline] ++ fn deref(&self) -> &str { ++ self.as_str() ++ } ++} ++ ++impl DerefMut for CompactCowStr<'_> { ++ #[inline] ++ fn deref_mut(&mut self) -> &mut str { ++ self.as_mut_str() ++ } ++} ++ ++impl AsRef for CompactCowStr<'_> { ++ #[inline] ++ fn as_ref(&self) -> &str { ++ self.as_str() ++ } ++} ++ ++impl AsRef<[u8]> for CompactCowStr<'_> { ++ #[inline] ++ fn as_ref(&self) -> &[u8] { ++ self.as_bytes() ++ } ++} ++ ++impl<'a> Borrow for CompactCowStr<'a> { ++ #[inline] ++ fn borrow(&self) -> &str { ++ self.as_str() ++ } ++} ++ ++impl<'a> BorrowMut for CompactCowStr<'a> { ++ #[inline] ++ fn borrow_mut(&mut self) -> &mut str { ++ self.as_mut_str() ++ } ++} ++ ++impl Eq for CompactCowStr<'_> {} ++ ++impl> PartialEq for CompactCowStr<'_> { ++ fn eq(&self, other: &T) -> bool { ++ self.as_str() == other.as_ref() ++ } ++} ++ ++impl PartialEq> for String { ++ fn eq(&self, other: &CompactCowStr<'_>) -> bool { ++ self.as_str() == other.as_str() ++ } ++} ++ ++impl PartialEq> for &str { ++ fn eq(&self, other: &CompactCowStr<'_>) -> bool { ++ *self == other.as_str() ++ } ++} ++ ++impl<'a> PartialEq> for Cow<'a, str> { ++ fn eq(&self, other: &CompactCowStr<'_>) -> bool { ++ *self == other.as_str() ++ } ++} ++ ++impl Ord for CompactCowStr<'_> { ++ fn cmp(&self, other: &Self) -> Ordering { ++ self.as_str().cmp(other.as_str()) ++ } ++} ++ ++impl PartialOrd for CompactCowStr<'_> { ++ fn partial_cmp(&self, other: &Self) -> Option { ++ Some(self.cmp(other)) ++ } ++} ++ ++impl Hash for CompactCowStr<'_> { ++ fn hash(&self, state: &mut H) { ++ self.as_str().hash(state) ++ } ++} ++ ++impl<'a> From<&'a str> for CompactCowStr<'_> { ++ #[inline] ++ #[track_caller] ++ fn from(s: &'a str) -> Self { ++ CompactCowStr::new(s) ++ } ++} ++ ++impl From for CompactCowStr<'_> { ++ #[inline] ++ #[track_caller] ++ fn from(s: String) -> Self { ++ CompactString::from(s).into() ++ } ++} ++ ++impl<'a> From<&'a String> for CompactCowStr<'_> { ++ #[inline] ++ #[track_caller] ++ fn from(s: &'a String) -> Self { ++ CompactCowStr::new(s) ++ } ++} ++ ++impl<'a> From> for CompactCowStr<'_> { ++ fn from(cow: Cow<'a, str>) -> Self { ++ match cow { ++ Cow::Borrowed(s) => s.into(), ++ // we separate these two so we can re-use the underlying buffer in the owned case ++ Cow::Owned(s) => s.into(), ++ } ++ } ++} ++ ++impl From> for CompactCowStr<'_> { ++ #[inline] ++ #[track_caller] ++ fn from(b: Box) -> Self { ++ CompactString::from(b).into() ++ } ++} ++ ++impl From> for String { ++ #[inline] ++ fn from(s: CompactCowStr<'_>) -> Self { ++ s.into_string() ++ } ++} ++ ++impl<'a> From> for Cow<'a, str> { ++ #[inline] ++ fn from(s: CompactCowStr<'a>) -> Self { ++ s.0.into_cow() ++ } ++} ++ ++impl<'a> From<&'a CompactCowStr<'_>> for Cow<'a, str> { ++ #[inline] ++ fn from(s: &'a CompactCowStr<'_>) -> Self { ++ Self::Borrowed(s.as_str()) ++ } ++} ++ ++#[rustversion::since(1.60)] ++#[cfg(target_has_atomic = "ptr")] ++impl From> for alloc::sync::Arc { ++ fn from(value: CompactCowStr<'_>) -> Self { ++ Self::from(value.as_str()) ++ } ++} ++ ++impl From> for alloc::rc::Rc { ++ fn from(value: CompactCowStr<'_>) -> Self { ++ Self::from(value.as_str()) ++ } ++} ++ ++impl From> for Box { ++ fn from(value: CompactCowStr<'_>) -> Self { ++ if value.is_heap_allocated() { ++ value.into_string().into_boxed_str() ++ } else { ++ Box::from(value.as_str()) ++ } ++ } ++} ++ ++#[cfg(feature = "std")] ++impl From> for std::ffi::OsString { ++ fn from(value: CompactCowStr<'_>) -> Self { ++ Self::from(value.into_string()) ++ } ++} ++ ++#[cfg(feature = "std")] ++impl From> for std::path::PathBuf { ++ fn from(value: CompactCowStr<'_>) -> Self { ++ Self::from(std::ffi::OsString::from(value)) ++ } ++} ++ ++#[cfg(feature = "std")] ++impl AsRef for CompactCowStr<'_> { ++ fn as_ref(&self) -> &std::path::Path { ++ std::path::Path::new(self.as_str()) ++ } ++} ++ ++impl From> for alloc::vec::Vec { ++ fn from(value: CompactCowStr<'_>) -> Self { ++ if value.is_heap_allocated() { ++ value.into_string().into_bytes() ++ } else { ++ value.as_bytes().to_vec() ++ } ++ } ++} ++ ++impl<'a> FromStr for CompactCowStr<'a> { ++ type Err = core::convert::Infallible; ++ fn from_str(s: &str) -> Result, Self::Err> { ++ Ok(CompactCowStr::from(s)) ++ } ++} ++ ++impl fmt::Debug for CompactCowStr<'_> { ++ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ++ fmt::Debug::fmt(self.as_str(), f) ++ } ++} ++ ++impl fmt::Display for CompactCowStr<'_> { ++ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ++ fmt::Display::fmt(self.as_str(), f) ++ } ++} ++ ++impl FromIterator for CompactCowStr<'_> ++where ++ CompactString: FromIterator, ++{ ++ fn from_iter>(iter: T) -> Self { ++ CompactString::from_iter(iter).into() ++ } ++} ++ ++impl<'a> FromIterator> for String { ++ fn from_iter>>(iter: T) -> Self { ++ let mut iterator = iter.into_iter(); ++ match iterator.next() { ++ None => String::new(), ++ Some(buf) => { ++ let mut buf = buf.into_string(); ++ buf.extend(iterator); ++ buf ++ } ++ } ++ } ++} ++ ++impl<'a> Extend> for String { ++ fn extend>>(&mut self, iter: T) { ++ for s in iter { ++ self.push_str(&s); ++ } ++ } ++} ++ ++impl<'a> Extend> for Cow<'_, str> { ++ fn extend>>(&mut self, iter: T) { ++ self.to_mut().extend(iter); ++ } ++} ++ ++impl<'a, S> Extend for CompactCowStr<'a> ++where ++ CompactString: Extend, ++{ ++ fn extend>(&mut self, iter: T) { ++ self.to_mut().extend(iter); ++ } ++} ++ ++impl core::fmt::Write for CompactCowStr<'_> { ++ fn write_str(&mut self, s: &str) -> fmt::Result { ++ self.to_mut().write_str(s) ++ } ++ ++ fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { ++ self.to_mut().write_fmt(args) ++ } ++} ++ ++impl Add<&str> for CompactCowStr<'_> { ++ type Output = Self; ++ fn add(mut self, rhs: &str) -> Self::Output { ++ self.push_str(rhs); ++ self ++ } ++} ++ ++impl AddAssign<&str> for CompactCowStr<'_> { ++ fn add_assign(&mut self, rhs: &str) { ++ self.push_str(rhs); ++ } ++} +diff --git a/src/lib.rs b/src/lib.rs +index fe74f13..d7a52cd 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -47,6 +47,9 @@ use core::{ + #[cfg(feature = "std")] + use std::ffi::OsStr; + ++mod cow; ++pub use cow::CompactCowStr; ++ + mod features; + mod macros; + mod unicode_data; +@@ -225,7 +228,7 @@ impl CompactString { + /// const LONG: CompactString = CompactString::new_inline("this is a long string that can't be stored on the stack"); + /// ``` + #[inline] +- pub const fn new_inline(text: &str) -> Self { ++ pub const fn new_inline(text: &'static str) -> Self { + CompactString(Repr::new_inline(text)) + } + +@@ -590,7 +593,7 @@ impl CompactString { + /// ``` + #[inline] + pub fn as_bytes(&self) -> &[u8] { +- &self.0.as_slice()[..self.len()] ++ self.0.as_slice() + } + + // TODO: Implement a `try_as_mut_slice(...)` that will fail if it results in cloning? +diff --git a/src/repr/capacity.rs b/src/repr/capacity.rs +index 778309a..a775e55 100644 +--- a/src/repr/capacity.rs ++++ b/src/repr/capacity.rs +@@ -23,6 +23,7 @@ const HEAP_MARKER: usize = { + /// + /// All bytes `255`, with the last being [`LastUtf8Char::Heap`], using the same amount of bytes + /// as `usize`. Example (64-bit): `[255, 255, 255, 255, 255, 255, 255, 216]` ++#[cfg(not(target_pointer_width = "64"))] + const CAPACITY_IS_ON_THE_HEAP: Capacity = Capacity(VALID_MASK | HEAP_MARKER); + + /// The maximum value we're able to store, e.g. on 64-bit arch this is 2^56 - 2. +@@ -101,7 +102,13 @@ impl Capacity { + /// stored on the heap + #[inline(always)] + pub fn is_heap(self) -> bool { +- self == CAPACITY_IS_ON_THE_HEAP ++ cfg_if::cfg_if! { ++ if #[cfg(target_pointer_width = "64")] { ++ false ++ } else { ++ self == CAPACITY_IS_ON_THE_HEAP ++ } ++ } + } + } + +diff --git a/src/repr/last_utf8_char.rs b/src/repr/last_utf8_char.rs +index fc28aee..a3b0af2 100644 +--- a/src/repr/last_utf8_char.rs ++++ b/src/repr/last_utf8_char.rs +@@ -239,6 +239,7 @@ pub enum LastUtf8Char { + + Heap = 216, + Static = 217, ++ Cow = 218, + } + + static_assertions::assert_eq_size!(LastUtf8Char, Option, u8); +diff --git a/src/repr/mod.rs b/src/repr/mod.rs +index 5f10383..853a40f 100644 +--- a/src/repr/mod.rs ++++ b/src/repr/mod.rs +@@ -17,7 +17,7 @@ mod inline; + mod iter; + mod last_utf8_char; + mod num; +-mod static_str; ++mod ref_str; + mod traits; + + use alloc::string::String; +@@ -26,13 +26,15 @@ use capacity::Capacity; + use heap::HeapBuffer; + use inline::InlineBuffer; + use last_utf8_char::LastUtf8Char; +-use static_str::StaticStr; ++use ref_str::{RefStr, StaticStr}; + pub use traits::IntoRepr; + + /// The max size of a string we can fit inline + pub const MAX_SIZE: usize = core::mem::size_of::(); + /// Used as a discriminant to identify different variants + pub const HEAP_MASK: u8 = LastUtf8Char::Heap as u8; ++/// Used for `RefStr` variant ++pub const REF_STR_MASK: u8 = LastUtf8Char::Cow as u8; + /// Used for `StaticStr` variant + pub const STATIC_STR_MASK: u8 = LastUtf8Char::Static as u8; + /// When our string is stored inline, we represent the length of the string in the last byte, offset +@@ -77,22 +79,37 @@ impl Repr { + } + + #[inline] +- pub const fn new_inline(text: &str) -> Self { ++ pub const fn new_inline(text: &'static str) -> Self { + let len = text.len(); + + if len <= MAX_SIZE { + let inline = InlineBuffer::new_const(text); + Repr::from_inline(inline) + } else { +- panic!("Inline string was too long, max length is `core::mem::size_of::()` bytes"); ++ let repr = StaticStr::new_static(text); ++ Repr::from_ref(repr) + } + } + + #[inline] + pub const fn from_static_str(text: &'static str) -> Self { +- let repr = StaticStr::new(text); +- // SAFETY: A `StaticStr` and `Repr` have the same size +- unsafe { core::mem::transmute(repr) } ++ let repr = StaticStr::new_static(text); ++ Repr::from_ref(repr) ++ } ++ ++ #[inline] ++ pub fn new_ref(text: &str) -> Self { ++ let len = text.len(); ++ ++ if len == 0 { ++ EMPTY ++ } else if len <= MAX_SIZE { ++ // SAFETY: We checked that the length of text is less than or equal to MAX_SIZE ++ let inline = unsafe { InlineBuffer::new(text) }; ++ Repr::from_inline(inline) ++ } else { ++ Repr::from_ref(RefStr::new_ref(text)) ++ } + } + + /// Create a [`Repr`] with the provided `capacity` +@@ -115,6 +132,15 @@ impl Repr { + Ok(Self::new(s)) + } + ++ /// Create a [`Repr`] from a slice of bytes that is UTF-8 ++ #[inline] ++ pub fn from_utf8_ref>(buf: B) -> Result { ++ // Get a &str from the Vec, failing if it's not valid UTF-8 ++ let s = core::str::from_utf8(buf.as_ref())?; ++ // Construct a Repr from the &str ++ Ok(Self::new_ref(s)) ++ } ++ + /// Create a [`Repr`] from a slice of bytes that is UTF-8, without validating that it is indeed + /// UTF-8 + /// +@@ -128,19 +154,6 @@ impl Repr { + // Create a Repr with enough capacity for the entire buffer + let mut repr = Repr::with_capacity(bytes_len); + +- // There's an edge case where the final byte of this buffer == `HEAP_MASK`, which is +- // invalid UTF-8, but would result in us creating an inline variant, that identifies as +- // a heap variant. If a user ever tried to reference the data at all, we'd incorrectly +- // try and read data from an invalid memory address, causing undefined behavior. +- if bytes_len == MAX_SIZE { +- let last_byte = bytes[bytes_len - 1]; +- // If we hit the edge case, reserve additional space to make the repr becomes heap +- // allocated, which prevents us from writing this last byte inline +- if last_byte >= 0b11000000 { +- repr.reserve(MAX_SIZE + 1); +- } +- } +- + // SAFETY: The caller is responsible for making sure the provided buffer is UTF-8. This + // invariant is documented in the public API + let slice = repr.as_mut_buf(); +@@ -154,6 +167,20 @@ impl Repr { + repr + } + ++ /// Create a [`Repr`] from a slice of bytes that is UTF-8, without validating that it is indeed ++ /// UTF-8 ++ /// ++ /// # Safety ++ /// * The caller must guarantee that `buf` is valid UTF-8 ++ #[inline] ++ pub unsafe fn from_utf8_unchecked_ref>(buf: B) -> Self { ++ let bytes = buf.as_ref(); ++ let str = core::str::from_utf8_unchecked(bytes); ++ ++ // Construct a Repr from the &str ++ Self::new_ref(str) ++ } ++ + /// Create a [`Repr`] from a [`String`], in `O(1)` time. We'll attempt to inline the string + /// if `should_inline` is `true` + /// +@@ -238,6 +265,18 @@ impl Repr { + } + } + ++ /// Converts a [`Repr`] into a [`Cow`], in `O(1)` time, if possible ++ #[inline] ++ pub fn into_cow<'a>(self) -> Cow<'a, str> { ++ if self.is_ref_str() { ++ // SAFETY: This was created from `RefStr`. ++ let ref_repr: RefStr = unsafe { core::mem::transmute(self) }; ++ Cow::Borrowed(ref_repr.get_text()) ++ } else { ++ self.into_string().into() ++ } ++ } ++ + /// Reserves at least `additional` bytes. If there is already enough capacity to store + /// `additional` bytes this is a no-op + #[inline] +@@ -247,9 +286,9 @@ impl Repr { + .checked_add(additional) + .expect("Attempted to reserve more than 'usize' bytes"); + +- if !self.is_static_str() && needed_capacity <= self.capacity() { ++ if !self.is_ref_str() && needed_capacity <= self.capacity() { + // we already have enough space, no-op +- // If self.is_static_str() is true, then we would have to convert ++ // If self.is_ref_str() is true, then we would have to convert + // it to other variants since static_str variant cannot be modified. + return; + } +@@ -446,7 +485,7 @@ impl Repr { + heap_buffer.capacity() + } + +- if let Some(s) = self.as_static_str() { ++ if let Some(s) = self.as_ref_str() { + s.len() + } else if self.is_heap_allocated() { + heap_capacity(self) +@@ -467,6 +506,18 @@ impl Repr { + last_byte == STATIC_STR_MASK + } + ++ #[inline(always)] ++ pub const fn is_ref_str(&self) -> bool { ++ let last_byte = self.last_byte(); ++ last_byte >= STATIC_STR_MASK ++ } ++ ++ #[inline(always)] ++ const fn is_non_static_str(&self) -> bool { ++ let last_byte = self.last_byte(); ++ last_byte == REF_STR_MASK ++ } ++ + #[inline] + #[rustversion::attr(since(1.64), const)] + pub fn as_static_str(&self) -> Option<&'static str> { +@@ -480,10 +531,32 @@ impl Repr { + } + + #[inline] +- fn as_static_variant_mut(&mut self) -> Option<&mut StaticStr> { +- if self.is_static_str() { +- // SAFETY: A `Repr` is transmuted from `StaticStr` +- let s: &mut StaticStr = unsafe { &mut *(self as *mut Self as *mut StaticStr) }; ++ pub const fn as_ref_str(&self) -> Option<&str> { ++ if self.is_ref_str() { ++ // SAFETY: A `Repr` is transmuted from `RefStr` ++ let s: &RefStr = unsafe { &*(self as *const Self as *const RefStr) }; ++ Some(s.get_text()) ++ } else { ++ None ++ } ++ } ++ ++ #[inline] ++ pub const fn as_non_static_str(&self) -> Option<&str> { ++ if self.is_non_static_str() { ++ // SAFETY: A `Repr` is transmuted from `RefStr` ++ let s: &RefStr = unsafe { &*(self as *const Self as *const RefStr) }; ++ Some(s.get_text()) ++ } else { ++ None ++ } ++ } ++ ++ #[inline] ++ fn as_ref_variant_mut(&mut self) -> Option<&mut RefStr> { ++ if self.is_ref_str() { ++ // SAFETY: A `Repr` is transmuted from `RefStr` ++ let s: &mut RefStr = unsafe { &mut *(self as *mut Self as *mut RefStr) }; + Some(s) + } else { + None +@@ -495,7 +568,7 @@ impl Repr { + /// # Safety + /// * Callers must guarantee that any modifications made to the buffer are valid UTF-8 + pub unsafe fn as_mut_buf(&mut self) -> &mut [u8] { +- if let Some(s) = self.as_static_str() { ++ if let Some(s) = self.as_ref_str() { + *self = Repr::new(s); + } + +@@ -524,7 +597,7 @@ impl Repr { + /// * `len` bytes in the buffer must be valid UTF-8 + /// * If the underlying buffer is stored inline, `len` must be <= MAX_SIZE + pub unsafe fn set_len(&mut self, len: usize) { +- if let Some(s) = self.as_static_variant_mut() { ++ if let Some(s) = self.as_ref_variant_mut() { + s.set_len(len); + } else if self.is_heap_allocated() { + // SAFETY: We just checked the discriminant to make sure we're heap allocated +@@ -581,6 +654,19 @@ impl Repr { + unsafe { core::mem::transmute(heap) } + } + ++ /// Reinterprets a [`RefStr`] into a [`Repr`] ++ /// ++ /// Note: This is safe because [`RefStr`] and [`Repr`] are the same size. We used to define ++ /// [`Repr`] as a `union` which implicitly transmuted between the two types, but that prevented ++ /// us from defining a "niche" value to make `Option` the same size as just ++ /// `CompactString` ++ /// Since [`StaticStr`] is alias of [`RefStr`], [`StaticStr`] also use this. ++ #[inline(always)] ++ const fn from_ref(heap: RefStr) -> Self { ++ // SAFETY: A `StaticStr` and `Repr` have the same size ++ unsafe { core::mem::transmute(heap) } ++ } ++ + /// Reinterprets a [`Repr`] as a [`HeapBuffer`] + /// + /// # SAFETY +@@ -654,6 +740,16 @@ impl Repr { + // SAFETY: An `InlineBuffer` and `Repr` have the same size + &mut *(self as *mut _ as *mut InlineBuffer) + } ++ ++ /// Makes this [`Repr`] as owned. ++ /// ++ /// When this [`Repr`] represent borrowed resource, clone them to new heap. ++ #[inline(always)] ++ pub fn make_owned(&mut self) { ++ if let Some(str) = self.as_non_static_str() { ++ *self = Repr::from_heap(HeapBuffer::new(str)) ++ } ++ } + } + + impl Clone for Repr { +diff --git a/src/repr/static_str.rs b/src/repr/ref_str.rs +similarity index 58% +rename from src/repr/static_str.rs +rename to src/repr/ref_str.rs +index 404cfd9..d5a422d 100644 +--- a/src/repr/static_str.rs ++++ b/src/repr/ref_str.rs +@@ -1,3 +1,4 @@ ++use core::marker::PhantomData; + use core::{ + mem, + ptr, +@@ -8,6 +9,7 @@ use core::{ + use super::{ + Repr, + MAX_SIZE, ++ REF_STR_MASK, + STATIC_STR_MASK, + }; + +@@ -17,18 +19,21 @@ pub(super) const DISCRIMINANT_SIZE: usize = MAX_SIZE - mem::size_of::<&'static s + /// The last byte is set to 0. + #[derive(Copy, Clone)] + #[repr(C)] +-pub struct StaticStr { ++pub struct RefStr<'a> { + ptr: ptr::NonNull, + len: usize, + #[allow(unused)] + discriminant: [u8; DISCRIMINANT_SIZE], ++ lifetime: PhantomData<&'a ()>, + } +-static_assertions::assert_eq_size!(StaticStr, Repr); ++pub type StaticStr = RefStr<'static>; ++ ++static_assertions::assert_eq_size!(RefStr, Repr); + static_assertions::assert_eq_size!(&'static str, (*const u8, usize)); + + impl StaticStr { + #[inline] +- pub const fn new(text: &'static str) -> Self { ++ pub const fn new_static(text: &'static str) -> Self { + let mut discriminant = [0; DISCRIMINANT_SIZE]; + discriminant[DISCRIMINANT_SIZE - 1] = STATIC_STR_MASK; + +@@ -38,12 +43,30 @@ impl StaticStr { + ptr: unsafe { ptr::NonNull::new_unchecked(text.as_ptr() as *mut _) }, + len: text.len(), + discriminant, ++ lifetime: PhantomData, ++ } ++ } ++} ++ ++impl<'a> RefStr<'a> { ++ #[inline] ++ pub const fn new_ref(text: &'a str) -> Self { ++ let mut discriminant = [0; DISCRIMINANT_SIZE]; ++ discriminant[DISCRIMINANT_SIZE - 1] = REF_STR_MASK; ++ ++ Self { ++ // SAFETY: `&str` must have a non-null, properly aligned ++ // address ++ ptr: unsafe { ptr::NonNull::new_unchecked(text.as_ptr() as *mut _) }, ++ len: text.len(), ++ discriminant, ++ lifetime: PhantomData, + } + } + + #[rustversion::attr(since(1.64), const)] +- pub(super) fn get_text(&self) -> &'static str { +- // SAFETY: `StaticStr` invariants requires it to be a valid str ++ pub(super) fn get_text(&self) -> &'a str { ++ // SAFETY: `RefStr` invariants requires it to be a valid str + unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.ptr.as_ptr(), self.len)) } + } + +@@ -51,6 +74,6 @@ impl StaticStr { + /// * `len` bytes in the buffer must be valid UTF-8 and + /// * `len` must be <= `self.get_text().len()` + pub(super) unsafe fn set_len(&mut self, len: usize) { +- *self = Self::new(&self.get_text()[..len]); ++ self.len = len; + } + } +diff --git a/src/repr/traits.rs b/src/repr/traits.rs +index 28b7b59..56b4a59 100644 +--- a/src/repr/traits.rs ++++ b/src/repr/traits.rs +@@ -37,7 +37,7 @@ impl IntoRepr for bool { + impl IntoRepr for char { + fn into_repr(self) -> Repr { + let mut buf = [0_u8; 4]; +- Repr::new_inline(self.encode_utf8(&mut buf)) ++ Repr::new(self.encode_utf8(&mut buf)) + } + } + +diff --git a/src/tests.rs b/src/tests.rs +index 77223b0..3e9ca87 100644 +--- a/src/tests.rs ++++ b/src/tests.rs +@@ -302,11 +302,11 @@ fn proptest_remove(#[strategy(rand_unicode_with_range(1..80))] mut control: Stri + + #[proptest] + #[cfg_attr(miri, ignore)] +-fn proptest_from_utf8_unchecked(#[strategy(rand_bytes())] bytes: Vec) { +- let compact = unsafe { CompactString::from_utf8_unchecked(&bytes) }; +- let std_str = unsafe { String::from_utf8_unchecked(bytes.clone()) }; ++fn proptest_from_utf8_unchecked(#[strategy(rand_unicode())] std_str: String) { ++ let bytes = std_str.as_bytes(); ++ let compact = unsafe { CompactString::from_utf8_unchecked(bytes) }; + +- // we might not make valid strings, but we should be able to read the underlying bytes ++ // we should be able to read the underlying bytes + assert_eq!(compact.as_bytes(), std_str.as_bytes()); + assert_eq!(compact.as_bytes(), bytes); + +@@ -315,7 +315,7 @@ fn proptest_from_utf8_unchecked(#[strategy(rand_bytes())] bytes: Vec) { + + // check if we were valid UTF-8, if so, assert the data written into the CompactString is + // correct +- let data_is_valid = core::str::from_utf8(&bytes); ++ let data_is_valid = core::str::from_utf8(bytes); + let compact_is_valid = core::str::from_utf8(compact.as_bytes()); + let std_str_is_valid = core::str::from_utf8(std_str.as_bytes()); + diff --git a/patches/core.patch b/patches/core.patch new file mode 100644 index 0000000..cebf1e8 --- /dev/null +++ b/patches/core.patch @@ -0,0 +1,50 @@ +diff --git a/src/error.rs b/src/error.rs +index a3f2b76..aed157c 100644 +--- a/src/error.rs ++++ b/src/error.rs +@@ -99,6 +99,19 @@ pub trait Error: Debug + Display { + TypeId::of::() + } + ++ #[doc(hidden)] ++ #[unstable( ++ feature = "error_type_id", ++ reason = "this is memory-unsafe to override in user code", ++ issue = "60784" ++ )] ++ fn type_name(&self) -> &'static str ++ where ++ Self: 'static, ++ { ++ core::any::type_name::() ++ } ++ + /// ``` + /// if let Err(e) = "xc".parse::() { + /// // Print `e` itself, no need for description(). +diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs +index 1324fb6..a98bf88 100644 +--- a/src/fmt/mod.rs ++++ b/src/fmt/mod.rs +@@ -18,7 +18,7 @@ mod float; + #[cfg(no_fp_fmt_parse)] + mod nofloat; + mod num; +-mod rt; ++pub mod rt; + + #[stable(feature = "fmt_flags_align", since = "1.28.0")] + #[cfg_attr(not(test), rustc_diagnostic_item = "Alignment")] +diff --git a/src/fmt/rt.rs b/src/fmt/rt.rs +index 92626fe..c220923 100644 +--- a/src/fmt/rt.rs ++++ b/src/fmt/rt.rs +@@ -89,7 +89,7 @@ pub struct Argument<'a> { + #[rustc_diagnostic_item = "ArgumentMethods"] + impl<'a> Argument<'a> { + #[inline(always)] +- fn new<'b, T>(x: &'b T, f: fn(&T, &mut Formatter<'_>) -> Result) -> Argument<'b> { ++ pub fn new<'b, T>(x: &'b T, f: fn(&T, &mut Formatter<'_>) -> Result) -> Argument<'b> { + // SAFETY: `mem::transmute(x)` is safe because + // 1. `&'b T` keeps the lifetime it originated with `'b` + // (so as to not have an unbounded lifetime) diff --git a/patches/num-bigint-0.4.5.patch b/patches/num-bigint-0.4.5.patch new file mode 100644 index 0000000..ec67de3 --- /dev/null +++ b/patches/num-bigint-0.4.5.patch @@ -0,0 +1,25 @@ +diff --git a/src/lib.rs b/src/lib.rs +index b807fd2..b509dcd 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -222,6 +222,7 @@ impl fmt::Display for TryFromBigIntError { + + pub use crate::biguint::BigUint; + pub use crate::biguint::ToBigUint; ++pub use crate::biguint::IntDigits; + pub use crate::biguint::U32Digits; + pub use crate::biguint::U64Digits; + +diff --git a/src/biguint.rs b/src/biguint.rs +index 4150605..f745241 100644 +--- a/src/biguint.rs ++++ b/src/biguint.rs +@@ -1071,7 +1071,7 @@ impl num_traits::ToBytes for BigUint { + } + } + +-pub(crate) trait IntDigits { ++pub trait IntDigits { + fn digits(&self) -> &[BigDigit]; + fn digits_mut(&mut self) -> &mut Vec; + fn normalize(&mut self); diff --git a/patches/serde_json-1.0.117.patch b/patches/serde_json-1.0.117.patch new file mode 100644 index 0000000..042b657 --- /dev/null +++ b/patches/serde_json-1.0.117.patch @@ -0,0 +1,44 @@ +diff --git a/src/de.rs b/src/de.rs +index c7774f6..5b896ef 100644 +--- a/src/de.rs ++++ b/src/de.rs +@@ -4,7 +4,7 @@ use crate::error::{Error, ErrorCode, Result}; + #[cfg(feature = "float_roundtrip")] + use crate::lexical; + use crate::number::Number; +-use crate::read::{self, Fused, Reference}; ++use crate::read::{self, Fused}; + use alloc::string::String; + use alloc::vec::Vec; + #[cfg(feature = "float_roundtrip")] +@@ -19,7 +19,7 @@ use serde::forward_to_deserialize_any; + #[cfg(feature = "arbitrary_precision")] + use crate::number::NumberDeserializer; + +-pub use crate::read::{Read, SliceRead, StrRead}; ++pub use crate::read::{Read, Reference, SliceRead, StrRead}; + + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] +diff --git a/src/error.rs b/src/error.rs +index fbf9eb1..b6fb633 100644 +--- a/src/error.rs ++++ b/src/error.rs +@@ -233,7 +233,7 @@ struct ErrorImpl { + column: usize, + } + +-pub(crate) enum ErrorCode { ++pub enum ErrorCode { + /// Catchall for syntax error messages + Message(Box), + +@@ -312,7 +312,7 @@ pub(crate) enum ErrorCode { + + impl Error { + #[cold] +- pub(crate) fn syntax(code: ErrorCode, line: usize, column: usize) -> Self { ++ pub fn syntax(code: ErrorCode, line: usize, column: usize) -> Self { + Error { + err: Box::new(ErrorImpl { code, line, column }), + } diff --git a/src/discriminant.rs b/src/discriminant.rs new file mode 100644 index 0000000..ba08949 --- /dev/null +++ b/src/discriminant.rs @@ -0,0 +1,44 @@ +use std::num::NonZeroI64; + +static mut DISCRIMINANT: NonZeroI64 = unsafe { NonZeroI64::new_unchecked(-1) }; +static mut DISC_STR: String = String::new(); +static mut DISC_LATEX: String = String::new(); +#[cfg(test)] +pub static DISC_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + +pub fn get() -> NonZeroI64 { + unsafe { DISCRIMINANT } +} + +pub fn get_str() -> &'static str { + unsafe { &*DISC_STR } +} + +pub fn get_latex() -> &'static str { + unsafe { &*DISC_LATEX } +} + +pub fn is4kp1() -> bool { + unsafe { DISCRIMINANT.get() & 3 == 1 } +} + +pub unsafe fn set(d: NonZeroI64) -> anyhow::Result<()> { + // this is a critical section, but we ensure only called once or using lock. + unsafe { + if let Some(e) = d.get().checked_isqrt() + && e * e == d.get() + { + anyhow::bail!("discriminant can't be a square"); + } + DISCRIMINANT = d; + #[allow(clippy::deref_addrof)] + if d.get() == -1 { + "i".clone_into(&mut *&raw mut DISC_STR); + "\\mathrm i".clone_into(&mut *&raw mut DISC_LATEX); + } else { + DISC_STR = format!("√{d}"); + DISC_LATEX = format!("\\sqrt{{{d}}}") + }; + Ok(()) + } +} diff --git a/src/factor.rs b/src/factor.rs new file mode 100644 index 0000000..9072e09 --- /dev/null +++ b/src/factor.rs @@ -0,0 +1,90 @@ +use num::BigUint; +use serde::Deserialize; +use std::{ + collections::{ + btree_map::Entry::{Occupied, Vacant}, + BTreeMap, + }, + fs, + io::{BufReader, Write}, + process::{Command, Stdio}, + time::{Duration, Instant}, +}; + +#[derive(Deserialize)] +struct YafuOutput { + #[serde(rename = "factors-prime")] + factors: Vec, +} + +pub fn factor(n: &BigUint) -> anyhow::Result> { + let t1 = std::time::Instant::now(); + let td = unsafe { std::mem::transmute::(t1) }; + let result = format!("../data/factor/q{}.json", td.as_nanos()); + let mut child = Command::new("yafu") + .arg("factor(@)") + .arg("-factorjson") + .arg(&result) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + let stdin = child + .stdin + .as_mut() + .ok_or_else(|| anyhow::anyhow!("no stdin"))?; + write!(stdin, "{n}")?; + child.wait()?; + + let file = fs::File::open(&result)?; + + struct Guard(String); + + impl Drop for Guard { + fn drop(&mut self) { + let _ = fs::remove_file(&self.0); + } + } + + let _guard = Guard(result); + let reader = BufReader::new(file); + let output = serde_json::from_reader::<_, YafuOutput>(reader)?; + + let mut factors = BTreeMap::new(); + for factor in output.factors { + let factor = factor.parse::()?; + match factors.entry(factor) { + Occupied(mut occupied) => *occupied.get_mut() += 1, + Vacant(vacant) => { + vacant.insert(1); + } + } + } + + Ok(factors.into_iter().collect()) +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use num::BigUint; + + use super::factor; + + #[test] + #[rustfmt::skip] + fn test_factor() { + let n = BigUint::from_str(&"4".repeat(67)).unwrap(); + let result = factor(&n).unwrap(); + assert_eq!( + result, + &[ + (BigUint::from(2u32), 2), + (BigUint::from_str("493121").unwrap(), 1), + (BigUint::from_str("79863595778924342083").unwrap(), 1), + (BigUint::from_str("28213380943176667001263153660999177245677").unwrap(), 1), + ] + ); + } +} diff --git a/src/ideal.rs b/src/ideal.rs new file mode 100644 index 0000000..542c19d --- /dev/null +++ b/src/ideal.rs @@ -0,0 +1,317 @@ +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; + +use num::{ + bigint::{IntDigits, Sign}, + BigInt, BigUint, Integer, One, Signed, Zero, +}; +use smallvec::{smallvec_inline, SmallVec}; + +use crate::{discriminant, factor::factor, pell, qi::QI, qr::quadratic_residue}; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct Ideal(SmallVec<[QI; 2]>); + +impl Ideal { + #[inline] + pub fn principal(x: QI) -> Self { + Self(unsafe { SmallVec::from_const_with_len_unchecked([x, QI::ZERO], 1) }) + } + + #[inline] + pub fn is_principal(&self) -> bool { + self.0.len() == 1 + } + + pub fn reduce(&mut self) { + if self.0.len() < 2 { + return; + } + let mut i = self.0.len(); + while i >= 3 { + let [a, b, c] = unsafe { self.0.get_many_unchecked_mut([i - 3, i - 2, i - 1]) }; + QI::reduce3(a, b, c); + i -= 1; + } + let [a, b] = unsafe { self.0.get_many_unchecked_mut([0, 1]) }; + QI::reduce2(a, b); + let a = core::mem::take(a); + let b = core::mem::take(b); + *self = if b.is_zero() { + Self::principal(a) + } else { + Self(smallvec_inline![a, b]) + } + } + + pub fn norm(&self) -> BigUint { + match &*self.0 { + [q] => q.norm().into_parts().1, + [q, r] => { + let a = q.norm(); + let b = r.norm(); + let c = q.dot(r); + a.magnitude().gcd(b.magnitude()).gcd(c.magnitude()) + } + _ => panic!("please reduce first before calling norm"), + } + } + + pub fn is_multiple_of(&self, other: &Self) -> bool { + let a = self.norm(); + let b = other.norm(); + if !a.is_multiple_of(&b) { + return false; + } + for qa in &self.0 { + for qb in &other.0 { + if !qa.dot(qb).magnitude().is_multiple_of(&b) { + return false; + } + } + } + true + } + + /// p should be a prime, otherwise UB. + pub fn factor_prime(p: &BigUint) -> SmallVec<[Ideal; 2]> { + let d = discriminant::get(); + let e = d.get() & 3 == 1; + let q = if e { p * 2u32 } else { p.clone() }; + + if e && p.digits() == [2] { + // special case + return if d.get() & 7 == 1 { + // splits + let ideal = Self(smallvec_inline![ + BigInt::from(q.clone()).into(), + QI { + a: BigInt::one(), + b: BigInt::one(), + } + ]); + let ideal_ = Self(smallvec_inline![ + BigInt::from(q).into(), + QI { + a: -BigInt::one(), + b: BigInt::one() + } + ]); + smallvec_inline![ideal, ideal_] + } else { + // inert + let ideal = Self::principal(BigInt::from(q).into()); + unsafe { SmallVec::from_const_with_len_unchecked([ideal, Self::default()], 1) } + }; + } + + let mut a = BigUint::from(d.get().unsigned_abs()) % p; + // ramified + if a.is_zero() { + let ideal = Self(smallvec_inline![BigInt::from(q).into(), QI::imaginary()]); + let ideal_ = ideal.clone(); + return smallvec_inline![ideal, ideal_]; + } + + if d.get() < 0 { + a = p - a; + } + + if let Some(mut x) = quadratic_residue(&a, p) { + // splits + let one = if e { + x *= 2u32; + BigInt::from(2) + } else { + BigInt::one() + }; + let px = BigInt::from(x.clone()); + let nx = BigInt::from_biguint( + if p.digits() == [2] { + Sign::Plus + } else { + Sign::Minus + }, + x, + ); + let ideal = Self(smallvec_inline![ + BigInt::from(q.clone()).into(), + QI { + a: px, + b: one.clone(), + } + ]); + let ideal_ = Self(smallvec_inline![ + BigInt::from(q).into(), + QI { a: nx, b: one } + ]); + smallvec_inline![ideal, ideal_] + } else { + // inert + let ideal = Self::principal(BigInt::from(q).into()); + unsafe { SmallVec::from_const_with_len_unchecked([ideal, Self::default()], 1) } + } + } + + pub fn factor(&mut self) -> anyhow::Result> { + self.reduce(); + + let d = discriminant::get(); + let e = d.get() & 3 == 1; + if let [x] = &*self.0 + && x.b.is_zero() + && x.a.is_positive() + { + let n = x.a.magnitude(); + let factors = if e { factor(&(n / 2u32)) } else { factor(n) }?; + let mut result = Vec::with_capacity(factors.len() * 2); + for (p, a) in factors { + let mut is = Self::factor_prime(&p); + let num = is.len(); + unsafe { is.set_len(2) }; + let [i1, i2] = is + .into_inner() + .map_err(|_| anyhow::anyhow!("wrong implementation of Ideal::factor_prime"))?; + + match num { + 1 => result.push((i1, a)), // principal, skipped + 2 if i1 == i2 => result.push(( + if let Some(qi) = pell::work(d.get(), &p) { + Self::principal(qi) + } else { + i1 + }, + 2 * a, + )), + 2 => { + if let Some(qi) = pell::work(d.get(), &p) { + let mut qj = qi.clone(); + qj.a = -qj.a; + result.push((Self::principal(qi), a)); + result.push((Self::principal(qj), a)); + } else { + result.push((i1, a)); + result.push((i2, a)); + } + } + _ => anyhow::bail!("unknown factor_prime error"), + } + } + Ok(result) + } else { + unimplemented!() + } + } + + pub fn latex(&self, f: &mut Formatter<'_>) { + let _ = f.write_str("\\left("); + let mut maybe_comma = ""; + for qi in &self.0 { + let _ = f.write_str(maybe_comma); + qi.latex(f); + maybe_comma = ","; + } + let _ = f.write_str("\\right)"); + } +} + +impl Display for Ideal { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut builder = f.debug_tuple(""); + for qi in &self.0 { + builder.field_with(|f| qi.fmt(f)); + } + builder.finish() + } +} + +impl FromStr for Ideal { + type Err = anyhow::Error; + + fn from_str(s: &str) -> anyhow::Result { + match s.parse::() { + Ok(x) => Ok(Ideal::principal(x)), + Err(e) => Err(e.into()), + } + } +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroI64; + + use num::BigInt; + use smallvec::smallvec; + + use super::{Ideal, QI}; + use crate::discriminant; + + fn check(ideal: Ideal, s: &str) { + let mut t = String::new(); + let mut fmt = core::fmt::Formatter::new(&mut t); + ideal.latex(&mut fmt); + assert_eq!(s, t); + } + + #[test] + #[rustfmt::skip] + fn latex_test_1() { + let _guard = discriminant::DISC_TEST_LOCK.lock().unwrap(); + unsafe { discriminant::set(NonZeroI64::new(-6).unwrap()).unwrap() }; + + let ideal = Ideal(smallvec![ + QI::from(BigInt::from(-15)), + QI::from(BigInt::from(-1)), + QI::from(BigInt::from(0)), + QI::from(BigInt::from(1)), + QI::from(BigInt::from(42)), + + QI { a: 0.into(), b: (-2).into() }, + QI { a: 0.into(), b: (-1).into() }, + QI { a: 0.into(), b: 1.into() }, + QI { a: 0.into(), b: 2.into() }, + + QI { a: (-1).into(), b: (-2).into() }, + QI { a: 1.into(), b: (-1).into() }, + QI { a: 2.into(), b: 1.into() }, + QI { a: (-2).into(), b: 2.into() }, + ]); + + check(ideal, r"\left(-15,-1,0,1,42,-2\sqrt{-6},-\sqrt{-6},\sqrt{-6},2\sqrt{-6},-1-2\sqrt{-6},1-\sqrt{-6},2+\sqrt{-6},-2+2\sqrt{-6}\right)"); + } + + #[test] + #[rustfmt::skip] + fn latex_test_2() { + let _guard = discriminant::DISC_TEST_LOCK.lock().unwrap(); + unsafe { discriminant::set(NonZeroI64::new(-7).unwrap()).unwrap() }; + + let ideal = Ideal(smallvec![ + QI::from(BigInt::from(-30)), + QI::from(BigInt::from(-2)), + QI::from(BigInt::from(0)), + QI::from(BigInt::from(2)), + QI::from(BigInt::from(42)), + + QI { a: 0.into(), b: (-4).into() }, + QI { a: 0.into(), b: (-2).into() }, + QI { a: 0.into(), b: 2.into() }, + QI { a: 0.into(), b: 4.into() }, + + QI { a: (-2).into(), b: (-4).into() }, + QI { a: 2.into(), b: (-2).into() }, + QI { a: 4.into(), b: 2.into() }, + QI { a: (-4).into(), b: 4.into() }, + + QI { a: 3.into(), b: (-3).into() }, + QI { a: (-3).into(), b: (-1).into() }, + QI { a: (-1).into(), b: 1.into() }, + QI { a: 1.into(), b: 3.into() }, + ]); + + check(ideal, r"\left(-15,-1,0,1,21,-2\sqrt{-7},-\sqrt{-7},\sqrt{-7},2\sqrt{-7},-1-2\sqrt{-7},1-\sqrt{-7},2+\sqrt{-7},-2+2\sqrt{-7},\frac{3-3\sqrt{-7}}2,\frac{-3-\sqrt{-7}}2,\frac{-1+\sqrt{-7}}2,\frac{1+3\sqrt{-7}}2\right)"); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7a578fd --- /dev/null +++ b/src/main.rs @@ -0,0 +1,69 @@ +#![feature( + debug_closure_helpers, + fmt_helpers_for_derive, + fmt_internals, + get_many_mut, + isqrt, + let_chains, + raw_ref_op, + slice_ptr_get, + stmt_expr_attributes, +)] + +mod discriminant; +mod factor; +mod ideal; +mod pell; +mod qi; +mod qr; + +use core::{ + fmt::{rt::Argument, Arguments, Formatter}, + num::NonZeroI64, +}; +use std::io::Write; + +use ideal::Ideal; + +#[derive(clap::Parser)] +struct Args { + #[arg(short, value_name = "discriminant")] + D: NonZeroI64, + #[arg(short, long, value_name = "input")] + input: String, +} + +fn main() -> anyhow::Result<()> { + use clap::Parser; + + let args = Args::parse(); + unsafe { discriminant::set(args.D)? }; + + let mut ideal = args.input.parse::()?; + + let ideals = ideal.factor()?; + + { + fn latex_wrapper(ideal: &Ideal, fmt: &mut Formatter) -> core::fmt::Result { + Ok(ideal.latex(fmt)) + } + + let mut stdout = std::io::stdout(); + stdout.write_fmt(Arguments::new_v1( + &[""], + &[Argument::new(&ideal, latex_wrapper)], + ))?; + stdout.write_all(b"=")?; + for (ideal, exp) in ideals { + stdout.write_fmt(Arguments::new_v1( + &[""], + &[Argument::new(&ideal, latex_wrapper)], + ))?; + if exp > 1 { + write!(stdout, "^{exp}")?; + } + } + } + + Ok(()) +} diff --git a/src/pell.rs b/src/pell.rs new file mode 100644 index 0000000..0586bb1 --- /dev/null +++ b/src/pell.rs @@ -0,0 +1,421 @@ +use std::num::Wrapping; + +use hashbrown::HashSet; +use num::{ + bigint::{IntDigits, Sign}, + BigInt, BigUint, Integer, One, Signed, Zero, +}; + +use crate::{qi::QI, qr::quadratic_residue}; + +#[inline] +fn mod_2_64_signed(x: &BigInt) -> Wrapping { + let y = mod_2_64(x.magnitude()); + if x.is_negative() { + -y + } else { + y + } +} + +#[inline] +fn mod_2_64(x: &BigUint) -> Wrapping { + Wrapping(if let Some(r) = x.digits().first() { + *r + } else { + 0 + }) +} + +struct Mat2By2 { + a: BigUint, + b: BigUint, + c: BigUint, + d: BigUint, +} + +/// inner method of divide & conquer. +fn convergent_inner(qs: &[BigUint]) -> Mat2By2 { + if qs.len() == 1 { + return Mat2By2 { + a: qs[0].clone(), + b: BigUint::one(), + c: BigUint::one(), + d: BigUint::ZERO, + }; + } + + let (left, right) = unsafe { qs.split_at_unchecked(qs.len() / 2) }; + let left = convergent_inner(left); + let right = convergent_inner(right); + + // clippy is so stupid to discover that it is a matrix multiplication. + #[allow(clippy::suspicious_operation_groupings)] + Mat2By2 { + a: &left.a * &right.a + &left.b * &right.c, + b: &left.a * &right.b + &left.b * &right.d, + c: &left.c * &right.a + &left.d * &right.c, + d: &left.c * &right.b + &left.d * &right.d, + } +} + +/// generate convergents of given continued fraction. +fn convergent(qs: &[BigUint]) -> (BigUint, BigUint) { + let mat = convergent_inner(qs); + (mat.a, mat.c) +} + +// solve P x^2 - Q x y + R y^2 where P, R = 0, 1 (R can't be zero) +fn solve_simple(P: &BigUint, Q: &BigUint, R: &BigUint) -> Option> { + if R.is_one() { + Some(Some((BigUint::ZERO, BigUint::one()))) + } else if P.is_one() { + Some(Some((BigUint::one(), BigUint::ZERO))) + } else if P.is_zero() { + let (q, r) = (R - 1u32).div_rem(Q); + Some(r.is_zero().then(|| (q, BigUint::one()))) + } else { + None + } +} + +/// Solve P x^2 - Q x y + R y^2 = 1 by convergents of x/y. +fn solve_by_convergents( + P: &BigUint, + Q: &BigUint, + R: &BigUint, + mut x: BigUint, + mut y: BigUint, +) -> Option<(BigUint, BigUint)> { + if let Some(res) = solve_simple(P, Q, R) { + return res; + } + + if Q.is_zero() { + return None; + } + + let (mut cx, mut cy, mut ccx, mut ccy) = ( + Wrapping(1u64), + Wrapping(0u64), + Wrapping(0u64), + Wrapping(1u64), + ); + + let P_ = mod_2_64(P); + let Q_ = mod_2_64(Q); + let R_ = mod_2_64(R); + + let empirical = (x.len() + y.len() + 5) * 2; + let mut qs = Vec::with_capacity(empirical as usize); + let mut indices = Vec::with_capacity(empirical as usize); + loop { + let (q, r) = x.div_rem(&y); + + let q_ = mod_2_64(&q); + ccx += cx * q_; + ccy += cy * q_; + + qs.push(q); + + if (ccx * (ccx * P_ - ccy * Q_) + ccy * ccy * R_).0 == 1 { + indices.push(qs.len()); + } + + if r.is_zero() { + for index in indices { + let (x, y) = convergent(unsafe { qs.get_unchecked(..index) }); + if &x * &x * P + &y * &y * R == &x * &y * Q + 1u32 { + return Some((x, y)); + } + } + return None; + } + + core::mem::swap(&mut cx, &mut ccx); + core::mem::swap(&mut cy, &mut ccy); + x = y; + y = r; + } +} + +/// A quadratic irrational (a + √d) / b, where b | a^2 - d. +#[derive(Clone, Hash, PartialEq, Eq)] +struct QIN { + a: BigInt, + b: BigInt, +} + +// (try -d 1263816031 --input 2 !) +/// Solve P x^2 - Q x y + R y^2 = ±1 by convergents of x. +fn solve_by_convergents_QIN( + P: &BigInt, + Q: &BigUint, + R: &BigUint, + D: u64, + mut x: QIN, +) -> Option<(/* bool, */ BigUint, BigUint)> { + if R.is_one() { + return Some((/* false, */ BigUint::ZERO, BigUint::one())); + } else if P.magnitude().is_one() { + return Some((/* P.is_negative(), */ BigUint::one(), BigUint::ZERO)); + } else if P.is_zero() { + let (q, r) = (R - 1u32).div_rem(Q); + if r.is_zero() { + return Some((/* false, */ q, BigUint::one())); + } + let (q, r) = (R + 1u32).div_rem(Q); + return r.is_zero().then(|| (/* true, */ q, BigUint::one())); + } + + let (mut cx, mut cy, mut ccx, mut ccy) = ( + Wrapping(1u64), + Wrapping(0u64), + Wrapping(0u64), + Wrapping(1u64), + ); + + let D_sqrt = D.isqrt(); + let P_ = mod_2_64_signed(P); + let Q_ = mod_2_64(Q); + let R_ = mod_2_64(R); + + let empirical = (D.ilog2() + 5) * 2; + let mut hash = HashSet::with_capacity(empirical as usize); + let mut qs = Vec::with_capacity(empirical as usize); + let mut indices = Vec::with_capacity(empirical as usize); + loop { + let q = (&x.a + (D_sqrt + x.b.is_negative() as u64)).magnitude() / x.b.magnitude(); + + let q_ = mod_2_64(&q); + ccx += cx * q_; + ccy += cy * q_; + + let trial = ccx * (ccx * P_ - ccy * Q_) + ccy * ccy * R_; + if trial.0 == 1 || trial.0 == u64::MAX { + indices.push(qs.len() + 1); + } + + if !hash.insert(x.clone()) { + qs.push(q); + + // println!("duplicate at {} tries, indices = {indices:?}", hash.len()); + for index in indices { + let (x, y) = convergent(unsafe { qs.get_unchecked(..index) }); + let x = BigInt::from(x); + let trial = &x * (&x * P - BigInt::from(&y * Q)) + BigInt::from(&y * &y * R); + if trial.magnitude().is_one() { + return Some((/* trial.is_negative(), */ x.into_parts().1, y)); + } + } + return None; + } + + core::mem::swap(&mut cx, &mut ccx); + core::mem::swap(&mut cy, &mut ccy); + + let q_ = BigInt::from(q); + x.a -= &x.b * &q_; + x.b = (D - &x.a * &x.a) / &x.b; + x.a = -x.a; + + qs.push(q_.into_parts().1) + } +} + +/// Solve x^2 + D y^2 = p. +pub fn work_neg(D: u64, p: &BigUint) -> Option { + let e = !D & 3 == 0; + + let mut qr = { + let mut d_p = BigUint::from(D) % p; + if !d_p.is_zero() { + d_p = p - d_p; + } + quadratic_residue(&d_p, &p)? + }; + + if e { + #[rustfmt::skip] + if p.digits() == [2] { + return (D == 7).then(|| QI { a: BigInt::one(), b: BigInt::one() }); + } + + if qr.is_even() { + qr = p - qr; + } + + let Q = qr; + let R = p; + let P = (&Q * &Q + D) / R / 4u32; + + // solve P y^2 - Q y z + R z^2 = 1 + let p_2 = &P * 2u32; + let r_2 = R * 2u32; + let (y, z) = if (&Q).max(&p_2) <= (&Q).max(&r_2) { + solve_by_convergents(&P, &Q, R, Q.clone(), p_2)? + } else { + solve_by_convergents(&P, &Q, R, r_2, Q.clone())? + }; + + let x = { + let a = &Q * &y; + let b = p * &z * 2u32; + if a < b { + b - a + } else { + a - b + } + }; + + Some(QI { + a: x.into(), + b: y.into(), + }) + } else { + let Q = qr; + let R = p; + let P = (&Q * &Q + D) / R; + // P = (Q * Q - D) / R + + // solve P y^2 - 2 Q y z + R z^2 = 1 + let (y, z) = if (&Q).max(&P) <= (&Q).max(&R) { + solve_by_convergents(&P, &(&Q * 2u32), R, Q.clone(), P.clone())? + } else { + solve_by_convergents(&P, &(&Q * 2u32), R, R.clone(), Q.clone())? + }; + + let x = { + let a = &Q * &y; + let b = p * &z; + if a < b { + b - a + } else { + a - b + } + }; + + Some(QI { + a: x.into(), + b: y.into(), + }) + } +} + +/// Solve x^2 - D y^2 = ±p. +pub fn work_pos(D: u64, p: &BigUint) -> Option { + let e = D & 3 == 1; + + let qr = quadratic_residue(&(BigUint::from(D) % p), &p)?; + + if e { + if p.digits() == [2] { + return if D > 16 && D & 7 == 1 { + // solving x^2 + x y - (D-1)/4 y^2 = 2, + // (let x = -2 z) + // -(D-1)/8 y^2 - y z + 2 z^2 = 1 + let qin = QIN { + a: BigInt::from(-1), + b: BigInt::from(D / 4), + }; + let (/* _is_neg, */ y, mut z) = solve_by_convergents_QIN( + &BigInt::from(-((D / 8) as i64)), + &BigUint::one(), + &BigUint::from(2u32), + D as u64, + qin, + )?; + + z *= 4u32; + let x = if y < z { &z - &y } else { &y - &z }; + + Some(QI { + a: x.into(), + b: y.into(), + }) + } else { + None + }; + } + + let Q = if qr.is_even() { p - qr } else { qr }; + let R = p; + let P = { + let (sign, mut part) = (BigInt::from(&Q * &Q) - D).into_parts(); + part /= R; + part /= 4u32; + BigInt::from_biguint(sign, part) + }; + + // solve P y^2 - Q y z + R z^2 = 1 + let qin = QIN { + a: if P.is_negative() { + BigInt::from_biguint(Sign::Minus, Q.clone()) + } else { + Q.clone().into() + }, + b: P.abs() * 2, + }; + + let (/* _is_neg, */ y, z) = solve_by_convergents_QIN(&P, &Q, R, D as u64, qin)?; + + let x = { + let a = &Q * &y; + let b = p * &z * 2u32; + if a < b { + b - a + } else { + a - b + } + }; + + Some(QI { + a: x.into(), + b: y.into(), + }) + } else { + let Q = qr; + let R = p; + let P = { + let (sign, mut part) = (BigInt::from(&Q * &Q) - D).into_parts(); + part /= R; + BigInt::from_biguint(sign, part) + }; + + // solve P y^2 - 2 Q y z + R z^2 = ±1 + let qin = QIN { + a: if P.is_negative() { + BigInt::from_biguint(Sign::Minus, Q.clone()) + } else { + Q.clone().into() + }, + b: P.abs(), + }; + + let (/* _is_neg, */ y, z) = solve_by_convergents_QIN(&P, &(&Q * 2u32), R, D as u64, qin)?; + + let x = { + let a = &Q * &y; + let b = p * &z; + if a < b { + b - a + } else { + a - b + } + }; + + Some(QI { + a: x.into(), + b: y.into(), + }) + } +} + +/// Solve x^2 - D y^2 = p. +pub fn work(D: i64, p: &BigUint) -> Option { + if D < 0 { + work_neg(D.unsigned_abs(), p) + } else { + work_pos(D as u64, p) + } +} diff --git a/src/qi.rs b/src/qi.rs new file mode 100644 index 0000000..386a92c --- /dev/null +++ b/src/qi.rs @@ -0,0 +1,302 @@ +use std::{ + fmt::{Display, Formatter, Write}, + str::FromStr, +}; + +use num::{bigint::Sign, BigInt, BigUint, Integer, One, Signed, Zero}; + +use crate::discriminant; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct QI { + pub a: BigInt, + pub b: BigInt, +} + +impl QI { + pub const ZERO: Self = Self { + a: BigInt::ZERO, + b: BigInt::ZERO, + }; + + #[inline] + pub fn is_zero(&self) -> bool { + self.a.is_zero() && self.b.is_zero() + } + + #[inline] + pub fn imaginary() -> Self { + let e = discriminant::is4kp1(); + Self { + a: BigInt::ZERO, + b: if e { BigInt::from(2) } else { BigInt::one() }, + } + } + + pub fn reduce2(a: &mut QI, b: &mut QI) -> bool { + if a.is_zero() { + core::mem::swap(a, b); + return false; + } + if b.is_zero() { + return false; + } + let mut na = a.gaussian_norm(); + let mut nb = b.gaussian_norm(); + if a.collinear_with(b) { + let ca = a.a.gcd(&b.a); + let mut cb = a.b.gcd(&b.b); + if a.a.sign() * b.a.sign() == Sign::Minus { + cb = -cb; + } + a.a = ca; + a.b = cb; + *b = Self::ZERO; + return true; + } + let mut dot = a.gaussian_dot(b); + let mut changed = false; + while !dot.is_zero() { + let mut progress = false; + let qb = BigInt::from_biguint(dot.sign(), (dot.magnitude() + &nb / 2u32) / &nb); + if !qb.is_zero() { + progress = true; + changed = true; + a.a -= &b.a * &qb; + a.b -= &b.b * &qb; + na = a.gaussian_norm(); + dot -= BigInt::from_biguint(qb.sign(), &nb * qb.magnitude()); + } + let qa = BigInt::from_biguint(dot.sign(), (dot.magnitude() + &na / 2u32) / &na); + if !qa.is_zero() { + progress = true; + changed = true; + b.a -= &a.a * &qa; + b.b -= &a.b * &qa; + nb = b.gaussian_norm(); + dot -= BigInt::from_biguint(qb.sign(), &nb * qa.magnitude()); + } + if !progress { + break; + } + } + if b.is_zero() { + core::mem::swap(a, b); + } + changed + } + + pub fn reduce3(a: &mut QI, b: &mut QI, c: &mut QI) { + loop { + let ab = QI::reduce2(a, b); + let bc = QI::reduce2(b, c); + let ca = QI::reduce2(c, a); + if !(ab || bc || ca) { + if !c.is_zero() { + if b.is_zero() { + core::mem::swap(b, c); + } else if a.is_zero() { + core::mem::swap(a, c); + } + } + assert!(c.is_zero()); + if a.is_zero() { + core::mem::swap(a, b); + } + return; + } + } + } + + #[inline] + fn collinear_with(&self, other: &Self) -> bool { + &self.a * &other.b == &self.b * &other.a + } + + #[inline] + fn gaussian_norm(&self) -> BigUint { + let ma = self.a.magnitude(); + let mb = self.b.magnitude(); + ma * ma + mb * mb + } + + #[inline] + fn gaussian_dot(&self, other: &Self) -> BigInt { + &self.a * &other.a + &self.b * &other.b + } + + #[inline] + pub fn norm(&self) -> BigInt { + let d = discriminant::get(); + let pre_norm = &self.a * &self.a - &self.b * &self.b * d.get(); + if d.get() & 3 == 1 { + pre_norm / 4 + } else { + pre_norm + } + } + + #[inline] + pub fn dot(&self, other: &Self) -> BigInt { + let d = discriminant::get(); + let pre_dot = &self.a * &other.a - &self.b * &other.b * d.get(); + if d.get() & 3 == 1 { + pre_dot / 2 + } else { + pre_dot * 2 + } + } + + pub fn latex(&self, f: &mut Formatter<'_>) { + let e = discriminant::is4kp1(); + let s = discriminant::get_latex(); + if self.b.is_zero() { + let _ = if e { + let a: BigInt = &self.a / 2; + a.fmt(f) + } else { + self.a.fmt(f) + }; + } else if self.a.is_zero() { + let b_: BigInt; + let b = if e { + b_ = &self.b / 2; + &b_ + } else { + &self.b + }; + if !b.magnitude().is_one() { + let _ = b.fmt(f); + } else if b.is_negative() { + let _ = f.write_char('-'); + } + let _ = f.write_str(s); + } else { + let div2 = e && self.a.is_odd(); + if div2 { + let _ = f.write_str("\\frac{"); + } + let (a_, b_): (BigInt, BigInt); + let (a, b) = if e && !self.a.is_odd() { + a_ = &self.a / 2; + b_ = &self.b / 2; + (&a_, &b_) + } else { + (&self.a, &self.b) + }; + let _ = a.fmt(f); + if b.magnitude().is_one() { + let _ = f.write_char(if b.is_negative() { '-' } else { '+' }); + } else { + let _ = write!(f, "{b:+}"); + } + let _ = f.write_str(s); + if div2 { + let _ = f.write_str("}2"); + } + } + } +} + +impl Display for QI { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let e = discriminant::is4kp1(); + let s = discriminant::get_str(); + if !e { + write!(f, "{}{:+}{s}", self.a, self.b)?; + } else if self.a.is_odd() { + write!(f, "{}.5{:+}.5{s}", &self.a / 2, &self.b / 2)?; + } else { + write!(f, "{}{:+}{s}", &self.a / 2, &self.b / 2)?; + } + Ok(()) + } +} + +impl From for QI { + fn from(n: BigInt) -> Self { + Self { + a: n, + b: BigInt::ZERO, + } + } +} + +impl FromStr for QI { + type Err = anyhow::Error; + + fn from_str(s: &str) -> anyhow::Result { + let e = discriminant::is4kp1(); + match s.parse::() { + Ok(x) => Ok(if e { x * 2 } else { x }.into()), + Err(e) => Err(e.into()), + } + } +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroI64; + + use num::BigInt; + + use super::{discriminant, QI}; + + fn check(qi: QI, s: &str) { + let mut t = String::new(); + let mut fmt = core::fmt::Formatter::new(&mut t); + qi.latex(&mut fmt); + assert_eq!(s, t); + } + + #[test] + #[rustfmt::skip] + fn latex_test_1() { + let _guard = discriminant::DISC_TEST_LOCK.lock().unwrap(); + unsafe { discriminant::set(NonZeroI64::new(-6).unwrap()).unwrap() }; + + check(QI::from(BigInt::from(-15)), "-15"); + check(QI::from(BigInt::from(-1)), "-1"); + check(QI::from(BigInt::from(0)), "0"); + check(QI::from(BigInt::from(1)), "1"); + check(QI::from(BigInt::from(42)), "42"); + + check(QI { a: 0.into(), b: (-2).into() }, r"-2\sqrt{-6}"); + check(QI { a: 0.into(), b: (-1).into() }, r"-\sqrt{-6}"); + check(QI { a: 0.into(), b: 1.into() }, r"\sqrt{-6}"); + check(QI { a: 0.into(), b: 2.into() }, r"2\sqrt{-6}"); + + check(QI { a: (-1).into(), b: (-2).into() }, r"-1-2\sqrt{-6}"); + check(QI { a: 1.into(), b: (-1).into() }, r"1-\sqrt{-6}"); + check(QI { a: 2.into(), b: 1.into() }, r"2+\sqrt{-6}"); + check(QI { a: (-2).into(), b: 2.into() }, r"-2+2\sqrt{-6}"); + } + + #[test] + #[rustfmt::skip] + fn latex_test_2() { + let _guard = discriminant::DISC_TEST_LOCK.lock().unwrap(); + unsafe { discriminant::set(NonZeroI64::new(-7).unwrap()).unwrap() }; + + check(QI::from(BigInt::from(-30)), "-15"); + check(QI::from(BigInt::from(-2)), "-1"); + check(QI::from(BigInt::from(0)), "0"); + check(QI::from(BigInt::from(2)), "1"); + check(QI::from(BigInt::from(42)), "21"); + + check(QI { a: 0.into(), b: (-4).into() }, r"-2\sqrt{-7}"); + check(QI { a: 0.into(), b: (-2).into() }, r"-\sqrt{-7}" ); + check(QI { a: 0.into(), b: 2.into() }, r"\sqrt{-7}" ); + check(QI { a: 0.into(), b: 4.into() }, r"2\sqrt{-7}" ); + + check(QI { a: (-2).into(), b: (-4).into() }, r"-1-2\sqrt{-7}" ); + check(QI { a: 2.into(), b: (-2).into() }, r"1-\sqrt{-7}" ); + check(QI { a: 4.into(), b: 2.into() }, r"2+\sqrt{-7}" ); + check(QI { a: (-4).into(), b: 4.into() }, r"-2+2\sqrt{-7}" ); + + check(QI { a: 3.into(), b: (-3).into() }, r"\frac{3-3\sqrt{-7}}2" ); + check(QI { a: (-3).into(), b: (-1).into() }, r"\frac{-3-\sqrt{-7}}2" ); + check(QI { a: (-1).into(), b: 1.into() }, r"\frac{-1+\sqrt{-7}}2" ); + check(QI { a: 1.into(), b: 3.into() }, r"\frac{1+3\sqrt{-7}}2" ); + } +} diff --git a/src/qr.rs b/src/qr.rs new file mode 100644 index 0000000..84dd4cc --- /dev/null +++ b/src/qr.rs @@ -0,0 +1,375 @@ +use std::ptr::NonNull; + +use num::{ + bigint::{IntDigits, RandBigInt}, + BigUint, Integer, One, Zero, +}; +use rand::thread_rng; + +#[repr(C)] +pub struct mpz_t { + pub alloc: i32, + pub size: i32, + pub d: NonNull, +} + +extern "C" { + #[link_name = "__gmpz_jacobi"] + pub fn mpz_jacobi(a: *const mpz_t, p: *const mpz_t) -> i32; +} + +pub fn jacobi(a: &BigUint, p: &BigUint) -> i32 { + let mpz_a = mpz_t { + alloc: 0, + size: a.len() as i32, + d: NonNull::from(a.digits()).as_non_null_ptr(), + }; + let mpz_p = mpz_t { + alloc: 0, + size: p.len() as i32, + d: NonNull::from(p.digits()).as_non_null_ptr(), + }; + unsafe { mpz_jacobi(&raw const mpz_a, &raw const mpz_p) } +} + +// 0 <= n <= p - 1, find x^2 = n +pub fn quadratic_residue(n: &BigUint, p: &BigUint) -> Option { + let mut b = BigUint::one(); + if n.is_zero() { + return Some(BigUint::ZERO); + } + if p.digits() == [2] { + return Some(b); + } + if jacobi(n, p) == -1 { + return None; + } + let mut rng = thread_rng(); + let (mut a, q) = loop { + let a = rng.gen_biguint_below(p); + let q = (&a * &a + p - n) % p; + if !q.is_zero() && jacobi(&q, p) == -1 { + break (a, q); + } + }; + + let mut exp = (p + 1u32) / 2u32; + + while exp.is_even() { + (a, b) = ((&a * &a + &b * &b * &q) % p, (&a * &b * 2u32) % p); + exp >>= 1; + } + + if exp.is_one() { + let pa = p - &a; + return Some(a.min(pa)); + } + + let mut fa = a.clone(); + let mut fb = b.clone(); + while !exp.is_one() { + exp >>= 1; + (a, b) = ((&a * &a + &b * &b * &q) % p, (&a * &b * 2u32) % p); + if exp.is_odd() { + (fa, fb) = ((&a * &fa + &b * &fb * &q) % p, (&a * &fb + &b * &fa) % p); + } + } + + let pa = p - &fa; + Some(fa.min(pa)) +} + +#[cfg(test)] +mod test { + use std::str::FromStr; + + use num::BigUint; + + use super::{jacobi, quadratic_residue}; + + #[test] + fn jacobi_test() { + #[rustfmt::skip] + let tests = [ + ("1", "3", 1), + ("2", "7", 1), + ("8", "11", -1), + ("17", "19", 1), + ("46", "59", 1), + ("72", "107", -1), + ("124", "139", 1), + ("41", "439", -1), + ("666", "769", -1), + ("1353", "1753", 1), + ("1659", "2767", -1), + ("2471", "7583", 1), + ("2467", "10691", -1), + ("4089", "24623", 1), + ("14774", "41621", 1), + ("31429", "109357", 1), + ("83155", "156119", 1), + ("272393", "319519", 1), + ("588653", "628687", 1), + ("83241", "1785313", 1), + ("1941865", "3203957", 1), + ("4303266", "8087797", 1), + ("11277967", "14133013", -1), + ("7204461", "27291521", 1), + ("2064350", "37031069", -1), + ("44045883", "111284171", -1), + ("144750115", "259088887", -1), + ("36272924", "536124899", 1), + ("472159819", "856934329", 1), + ("512216818", "1676155333", -1), + ("1388823058", "2193097129", -1), + ("2820232600", "5905596839", 1), + ("7554220953", "12044638633", 1), + ("11434836289", "25971824777", -1), + ("41003866862", "51243325643", -1), + ("54860380730", "116196121807", 1), + ("206616388015", "209100951571", -1), + ("110842361086", "308811437861", -1), + ("566437381214", "671804230807", 1), + ("123375109646", "1190541404827", 1), + ("777161274856", "4009854583837", -1), + ("3098687787664", "4597702669717", 1), + ("11164043099216", "11508469095737", 1), + ("15634502870591", "17979082298147", -1), + ("46726437032943", "53053506089663", -1), + ("78795987240025", "122315484257959", -1), + ("82720165318389", "236214642973717", -1), + ("230176878238786", "350944254440693", -1), + ("216838233790084", "663517437543577", 1), + ("145998398273791", "1595362964585927", -1), + ("3344049673165765", "4349598478138817", 1), + ("1456732815564264", "7003571800648531", 1), + ("3384584763097610", "15130135186707257", 1), + ("983687161411013", "23533622649649939", -1), + ("26157663619786901", "49053915500286077", 1), + ("16266239522862707", "119650523932982767", 1), + ("152434826414158203", "170047273414179779", 1), + ("41472518848515463", "504285318848955311", -1), + ("439776037087242555", "843544865001745171", -1), + ("99234610114196383", "1383648096200136539", -1), + ("652050283882559230", "4216863245806339321", 1), + ("3052448625901243692", "7248945724689640313", 1), + ("3414729853898199292", "10041292399808404403", -1), + ("3289098175140376023", "18705468446802001219", 1), + ("3036276261961113018", "47646031813904875277", -1), + ("68843147922708104117", "98079718604888635207", -1), + ("215633730182925452383", "217674878313690760771", 1), + ("1035456368116872818", "485727586661742137021", -1), + ("719852666000316991046", "895196684500006935853", 1), + ("1683296522008327350297", "1919548172354657933243", -1), + ("2648907197861518918900", "2652086957356950397849", -1), + ("5251892527858968387043", "7168068960158309361581", 1), + ("7979676508173392972037", "16342586575211966687203", -1), + ("12400744344545173924494", "36254616581882410348387", 1), + ("48663103552626272660986", "65144879782733718340033", 1), + ("81168780278010488932829", "94294938700405419954547", 1), + ("88168583794861406947900", "191934150586797200583877", 1), + ("96591903694265763185306", "476426946939536748711113", 1), + ("81810741928018841313873", "1059008249816352576294137", -1), + ("205630017807492163486853", "1659177233564189297488619", 1), + ("3091073576480822694500653", "3822534929973050934668713", -1), + ("3118250261977177402821085", "5870963667136145537453441", -1), + ("12968499099217375809440821", "14885309829027571177845113", 1), + ("22881385759262567927181626", "24310794574017373824215299", 1), + ("44307374829366108723981890", "67561168900184515767978673", 1), + ("50667677222899761146982116", "99361153507586476025937107", 1), + ("190795345923625039967337793", "278028935466309978080403029", -1), + ("450386463428427499579162352", "508635351542687116158948211", 1), + ("817770639323851228506863561", "1232182922314301453197340983", 1), + ("608638945055311644194203427", "2169026424346627223493270131", 1), + ("1810336609559042492106372610", "3318012661049816072440498333", 1), + ("5379922856000305013750249098", "5876194230736542092844682303", 1), + ("3657808355875219491909441510", "14510449892969287114486921321", -1), + ("20439222377536948729634776630", "22965938862697945056139880377", -1), + ("2363021530459461319533349898", "55479449737304695958942457233", -1), + ("16432606038300333659372723857", "82162963072002847934606020289", -1), + ("12648466534445759731600017009", "219575793441865445479078510141", -1), + ("322568325050805017490795795217", "522609180750994490465668406179", -1), + ("352738814168592766752159432055", "636734176452813891880996581901", -1), + ("728506403316379502870782186405", "1742888112058991884222027805983", -1), + ("1747905236769494734805047632045", "2987003706831951491565340322657", -1), + ("1306751603763397790249874018917", "9348824043598387217692889750939", -1), + ("4545107339459852760463828409950", "15163334322713411200005608240843", -1), + ("14486728587179404275536330955250", "35579326236006659618034639142903", -1), + ("25801023388793225005188501837317", "47370888945278469262088358500629", -1), + ("33263680658261791946050785899820", "135240390502273074641827407494351", 1), + ("288310461316710019332146435842389", "303910063100629585724311348193243", 1), + ("101420311457766216852923894199876", "447832946198002964274863595337723", 1), + ("18180602019068985712121554110357", "1094489494486506202663651688040331", 1), + ("556131465664495059496791699541363", "1301131272566153959621646724437437", 1), + ("2262468517850967279413396270832485", "5186529457535616677154281744156461", -1), + ("293060667627821121835189300396201", "8970512837254445059742471556256967", 1), + ("15126399334866744568540839008130363", "17093575876038436948530577791588151", -1), + ("1370164307297207850539405310604298", "34178544033254135131005587993338633", -1), + ("21487291103923513527578832647183224", "56748390428456543970438172869589613", -1), + ("45743304580051379836054444737401130", "101755805015141565189991756098311471", -1), + ("139142982774807352304433393757291487", "226895235806166973998530083828253861", 1), + ("409442986295701140154446247235330375", "518261062684509841992241662943086487", -1), + ("99577510417456020166146339559310823", "864040172367125613971205854929095503", 1), + ("1089470874565074238286040478869974158", "1546169986882663467057092395950178633", -1), + ("936304684367455668346518992560209722", "3893856432242300704534135322652943591", 1), + ("4880627387238252064884124197487940739", "6908933379859872287963745563223235379", 1), + ("16358676951156560926980694295882791793", "18456597409707363638988908551637739877", -1), + ("37081841953635682865877952704758247253", "37172517261598233403443979224236553977", 1), + ("43305137454008268086300879269348685299", "61743196222758696979062314064861805009", 1), + ("119291956873859150545925212701798813093", "139439329946982726687651398324801994431", 1), + ("159292864164049007001913292120495267987", "201706247291756054350779888609668868187", -1), + ]; + for (a, p, expected) in tests { + assert_eq!( + jacobi( + &BigUint::from_str(a).unwrap(), + &BigUint::from_str(p).unwrap() + ), + expected + ); + } + } + + #[test] + fn quadratic_residue_test() { + #[rustfmt::skip] + let tests = [ + ("1", "3", Some("1")), + ("4", "7", Some("2")), + ("8", "11", None), + ("17", "19", Some("6")), + ("46", "59", Some("20")), + ("72", "107", None), + ("124", "139", Some("47")), + ("41", "439", None), + ("666", "769", None), + ("1353", "1753", Some("236")), + ("1659", "2767", None), + ("2471", "7583", Some("2188")), + ("2467", "10691", None), + ("4089", "24623", Some("5034")), + ("14774", "41621", Some("16187")), + ("31429", "109357", Some("14992")), + ("83155", "156119", Some("71624")), + ("272393", "319519", Some("70578")), + ("588653", "628687", Some("287018")), + ("83241", "1785313", Some("453349")), + ("1941865", "3203957", Some("1456156")), + ("4303266", "8087797", Some("3022775")), + ("11277967", "14133013", None), + ("7204461", "27291521", Some("10372972")), + ("2064350", "37031069", None), + ("44045883", "111284171", None), + ("144750115", "259088887", None), + ("36272924", "536124899", Some("64560827")), + ("472159819", "856934329", Some("425685140")), + ("512216818", "1676155333", None), + ("1388823058", "2193097129", None), + ("2820232600", "5905596839", Some("815716192")), + ("7554220953", "12044638633", Some("5394405373")), + ("11434836289", "25971824777", None), + ("41003866862", "51243325643", None), + ("54860380730", "116196121807", Some("24386973969")), + ("206616388015", "209100951571", None), + ("110842361086", "308811437861", None), + ("566437381214", "671804230807", Some("45626083712")), + ("123375109646", "1190541404827", Some("503286545092")), + ("777161274856", "4009854583837", None), + ("3098687787664", "4597702669717", Some("810665395448")), + ("11164043099216", "11508469095737", Some("825616590659")), + ("15634502870591", "17979082298147", None), + ("46726437032943", "53053506089663", None), + ("78795987240025", "122315484257959", None), + ("82720165318389", "236214642973717", None), + ("230176878238786", "350944254440693", None), + ("216838233790084", "663517437543577", Some("206382186024904")), + ("145998398273791", "1595362964585927", None), + ("3344049673165765", "4349598478138817", Some("1735196737869083")), + ("1456732815564264", "7003571800648531", Some("2821658064570537")), + ("3384584763097610", "15130135186707257", Some("7386978532262161")), + ("983687161411013", "23533622649649939", None), + ("26157663619786901", "49053915500286077", Some("4487033576478061")), + ("16266239522862707", "119650523932982767", Some("53964040479935112")), + ("152434826414158203", "170047273414179779", Some("81850002819909599")), + ("41472518848515463", "504285318848955311", None), + ("439776037087242555", "843544865001745171", None), + ("99234610114196383", "1383648096200136539", None), + ("652050283882559230", "4216863245806339321", Some("478258949824049222")), + ("3052448625901243692", "7248945724689640313", Some("2104557789773277029")), + ("3414729853898199292", "10041292399808404403", None), + ("3289098175140376023", "18705468446802001219", Some("7069468207044251959")), + ("3036276261961113018", "47646031813904875277", None), + ("68843147922708104117", "98079718604888635207", None), + ("215633730182925452383", "217674878313690760771", Some("45983469400202548116")), + ("1035456368116872818", "485727586661742137021", None), + ("719852666000316991046", "895196684500006935853", Some("206575257889778717410")), + ("1683296522008327350297", "1919548172354657933243", None), + ("2648907197861518918900", "2652086957356950397849", None), + ("5251892527858968387043", "7168068960158309361581", Some("1386034441274780234708")), + ("7979676508173392972037", "16342586575211966687203", None), + ("12400744344545173924494", "36254616581882410348387", Some("17433509683739523361493")), + ("48663103552626272660986", "65144879782733718340033", Some("23672869521221832105174")), + ("81168780278010488932829", "94294938700405419954547", Some("10359921167924059092210")), + ("88168583794861406947900", "191934150586797200583877", Some("33982835166550513073866")), + ("96591903694265763185306", "476426946939536748711113", Some("187481042889215396871772")), + ("81810741928018841313873", "1059008249816352576294137", None), + ("205630017807492163486853", "1659177233564189297488619", Some("806626177865707860905010")), + ("3091073576480822694500653", "3822534929973050934668713", None), + ("3118250261977177402821085", "5870963667136145537453441", None), + ("12968499099217375809440821", "14885309829027571177845113", Some("5929264705643305645088767")), + ("22881385759262567927181626", "24310794574017373824215299", Some("8326044186850839591788671")), + ("44307374829366108723981890", "67561168900184515767978673", Some("4327542648176874703948942")), + ("50667677222899761146982116", "99361153507586476025937107", Some("42447464957260824412732673")), + ("190795345923625039967337793", "278028935466309978080403029", None), + ("450386463428427499579162352", "508635351542687116158948211", Some("250013263930350427649829762")), + ("817770639323851228506863561", "1232182922314301453197340983", Some("543389381371104788961995723")), + ("608638945055311644194203427", "2169026424346627223493270131", Some("221433468502740845461439689")), + ("1810336609559042492106372610", "3318012661049816072440498333", Some("553201865175413596632746289")), + ("5379922856000305013750249098", "5876194230736542092844682303", Some("2285842270456230917806776016")), + ("3657808355875219491909441510", "14510449892969287114486921321", None), + ("20439222377536948729634776630", "22965938862697945056139880377", None), + ("2363021530459461319533349898", "55479449737304695958942457233", None), + ("16432606038300333659372723857", "82162963072002847934606020289", None), + ("12648466534445759731600017009", "219575793441865445479078510141", None), + ("322568325050805017490795795217", "522609180750994490465668406179", None), + ("352738814168592766752159432055", "636734176452813891880996581901", None), + ("728506403316379502870782186405", "1742888112058991884222027805983", None), + ("1747905236769494734805047632045", "2987003706831951491565340322657", None), + ("1306751603763397790249874018917", "9348824043598387217692889750939", None), + ("4545107339459852760463828409950", "15163334322713411200005608240843", None), + ("14486728587179404275536330955250", "35579326236006659618034639142903", None), + ("25801023388793225005188501837317", "47370888945278469262088358500629", None), + ("33263680658261791946050785899820", "135240390502273074641827407494351", Some("3412308806058149067615126448325")), + ("288310461316710019332146435842389", "303910063100629585724311348193243", Some("48682590608341497842684025236561")), + ("101420311457766216852923894199876", "447832946198002964274863595337723", Some("199528309542956110338147834079419")), + ("18180602019068985712121554110357", "1094489494486506202663651688040331", Some("240040787125416300438793411307681")), + ("556131465664495059496791699541363", "1301131272566153959621646724437437", Some("246117912747567161597131167194287")), + ("2262468517850967279413396270832485", "5186529457535616677154281744156461", None), + ("293060667627821121835189300396201", "8970512837254445059742471556256967", Some("1851058237508064068417785204351771")), + ("15126399334866744568540839008130363", "17093575876038436948530577791588151", None), + ("1370164307297207850539405310604298", "34178544033254135131005587993338633", None), + ("21487291103923513527578832647183224", "56748390428456543970438172869589613", None), + ("45743304580051379836054444737401130", "101755805015141565189991756098311471", None), + ("139142982774807352304433393757291487", "226895235806166973998530083828253861", Some("57037210757541854348813322455596226")), + ("409442986295701140154446247235330375", "518261062684509841992241662943086487", None), + ("99577510417456020166146339559310823", "864040172367125613971205854929095503", Some("335073206022326861546046211292782766")), + ("1089470874565074238286040478869974158", "1546169986882663467057092395950178633", None), + ("936304684367455668346518992560209722", "3893856432242300704534135322652943591", Some("1346826092017176146299709042874113342")), + ("4880627387238252064884124197487940739", "6908933379859872287963745563223235379", Some("2054080291215141897195158542978888045")), + ("16358676951156560926980694295882791793", "18456597409707363638988908551637739877", None), + ("37081841953635682865877952704758247253", "37172517261598233403443979224236553977", Some("2430915567660493860431586755921695512")), + ("43305137454008268086300879269348685299", "61743196222758696979062314064861805009", Some("27250396381616513578989934719653037510")), + ("119291956873859150545925212701798813093", "139439329946982726687651398324801994431", Some("69507397118966317412972861759147500580")), + ("159292864164049007001913292120495267987", "201706247291756054350779888609668868187", None), + ]; + for (a, p, expected) in tests { + assert_eq!( + quadratic_residue( + &BigUint::from_str(a).unwrap(), + &BigUint::from_str(p).unwrap(), + ), + expected.map(|x| BigUint::from_str(x).unwrap()) + ); + } + } +}