commit aab3b3d5c0efca0023ec9ee6544cab5434ed3725 Author: imxyy_soope_ Date: Wed Dec 31 22:35:48 2025 +0800 feat: init diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8a4d91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ + +/.direnv/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..55dd3e0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2201 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "boxed_error" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17d4f95e880cfd28c4ca5a006cf7f6af52b4bcb7b5866f573b2faa126fb7affb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "calendrical_calculations" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7" +dependencies = [ + "core_maths", + "displaydoc", +] + +[[package]] +name = "capacity_builder" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2d24a6dcf0cd402a21b65d35340f3a49ff3475dc5fdac91d22d2733e6641c6" +dependencies = [ + "capacity_builder_macros", + "itoa", +] + +[[package]] +name = "capacity_builder_macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b4a6cae9efc04cc6cbb8faf338d2c497c165c83e74509cf4dbedea948bbf6e5" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "cc" +version = "1.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cooked-waker" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + +[[package]] +name = "deno_core" +version = "0.376.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829ee94e76838e99951cc7b3ccd31f68a220687f52229c0d6e135ff29b22f6d0" +dependencies = [ + "anyhow", + "az", + "bincode", + "bit-set", + "bit-vec", + "boxed_error", + "bytes", + "capacity_builder", + "cooked-waker", + "deno_core_icudata", + "deno_error", + "deno_ops", + "deno_path_util", + "deno_unsync", + "futures", + "indexmap", + "libc", + "parking_lot", + "percent-encoding", + "pin-project", + "serde", + "serde_json", + "serde_v8", + "smallvec", + "sourcemap", + "static_assertions", + "thiserror", + "tokio", + "url", + "v8", + "wasm_dep_analyzer", +] + +[[package]] +name = "deno_core_icudata" +version = "0.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4dccb6147bb3f3ba0c7a48e993bfeb999d2c2e47a81badee80e2b370c8d695" + +[[package]] +name = "deno_error" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3007d3f1ea92ea503324ae15883aac0c2de2b8cf6fead62203ff6a67161007ab" +dependencies = [ + "deno_error_macro", + "libc", + "serde", + "serde_json", + "tokio", + "url", +] + +[[package]] +name = "deno_error_macro" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b565e60a9685cdf312c888665b5f8647ac692a7da7e058a5e2268a466da8eaf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "deno_ops" +version = "0.252.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83a1be112eed3cb12e02c541f0da2a7357655aa519f3f180c405c0409cfa5ea" +dependencies = [ + "indexmap", + "proc-macro2", + "quote", + "stringcase", + "strum", + "strum_macros", + "syn", + "syn-match", + "thiserror", +] + +[[package]] +name = "deno_path_util" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c7e98943f0d068928906db0c7bde89de684fa32c6a8018caacc4cee2cdd72b" +dependencies = [ + "deno_error", + "percent-encoding", + "sys_traits", + "thiserror", + "url", +] + +[[package]] +name = "deno_unsync" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6742a724e8becb372a74c650a1aefb8924a5b8107f7d75b3848763ea24b27a87" +dependencies = [ + "futures-util", + "parking_lot", + "tokio", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "diplomat" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9adb46b05e2f53dcf6a7dfc242e4ce9eb60c369b6b6eb10826a01e93167f59c6" +dependencies = [ + "diplomat_core", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diplomat-runtime" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0569bd3caaf13829da7ee4e83dbf9197a0e1ecd72772da6d08f0b4c9285c8d29" + +[[package]] +name = "diplomat_core" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51731530ed7f2d4495019abc7df3744f53338e69e2863a6a64ae91821c763df1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "smallvec", + "strck", + "syn", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.0.8", + "windows-sys 0.59.0", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gzip-header" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" +dependencies = [ + "crc32fast", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "icu_calendar" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e" +dependencies = [ + "calendrical_calculations", + "displaydoc", + "icu_calendar_data", + "icu_locale", + "icu_locale_core", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_calendar_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d" + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_locale_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locale_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03e2fcaefecdf05619f3d6f91740e79ab969b4dd54f77cbf546b1d0d28e3147" + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "serde", + "stable_deref_trait", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if_chain" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "ixdtf" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mimalloc" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix-js" +version = "0.1.0" +dependencies = [ + "anyhow", + "bumpalo", + "deno_core", + "derive_more", + "hashbrown 0.16.1", + "mimalloc", + "nix-js-macros", + "regex", + "rnix", + "rustyline", + "string-interner", + "thiserror", + "v8", +] + +[[package]] +name = "nix-js-macros" +version = "0.1.0" +dependencies = [ + "convert_case 0.8.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "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-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "serde_core", + "writeable", + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "resb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a067ab3b5ca3b4dc307d0de9cf75f9f5e6ca9717b192b2f28a36c83e5de9e76" +dependencies = [ + "potential_utf", + "serde_core", +] + +[[package]] +name = "rnix" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f15e00b0ab43abd70d50b6f8cd021290028f9b7fdd7cdfa6c35997173bc1ba9" +dependencies = [ + "rowan", +] + +[[package]] +name = "rowan" +version = "0.15.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4f1e4a001f863f41ea8d0e6a0c34b356d5b733db50dadab3efef640bafb779b" +dependencies = [ + "countme", + "hashbrown 0.14.5", + "memoffset", + "rustc-hash 1.1.0", + "text-size", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustyline" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" +dependencies = [ + "bitflags", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_v8" +version = "0.285.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26d546283b1182f61e75b598da17f3babde4b25e479a5cb3fbb39d26cb928fd" +dependencies = [ + "deno_error", + "num-bigint", + "serde", + "smallvec", + "thiserror", + "v8", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "sourcemap" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccaaa78a0ca68b20f8f711eaa2522a00131c48a3de5b892ca5c36cec1ce9bb" +dependencies = [ + "base64-simd", + "bitvec", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "unicode-id-start", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strck" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42316e70da376f3d113a68d138a60d8a9883c604fe97942721ec2068dab13a9f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "string-interner" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0" +dependencies = [ + "hashbrown 0.15.5", + "serde", +] + +[[package]] +name = "stringcase" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72abeda133c49d7bddece6c154728f83eec8172380c80ab7096da9487e20d27c" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-match" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783c4140d7ed89f37116e865b49e5a9fdd28608b9071a9dd1e158b50fc0a31fc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sys_traits" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b61f4a25d0baba25511bed00c39c199d9a19cfd8107f4472724b72a84f530b1" +dependencies = [ + "sys_traits_macros", +] + +[[package]] +name = "sys_traits_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "181f22127402abcf8ee5c83ccd5b408933fec36a6095cf82cda545634692657e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "temporal_capi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a151e402c2bdb6a3a2a2f3f225eddaead2e7ce7dd5d3fa2090deb11b17aa4ed8" +dependencies = [ + "diplomat", + "diplomat-runtime", + "icu_calendar", + "icu_locale", + "num-traits", + "temporal_rs", + "timezone_provider", + "writeable", + "zoneinfo64", +] + +[[package]] +name = "temporal_rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88afde3bd75d2fc68d77a914bece426aa08aa7649ffd0cdd4a11c3d4d33474d1" +dependencies = [ + "core_maths", + "icu_calendar", + "icu_locale", + "ixdtf", + "num-traits", + "timezone_provider", + "tinystr", + "writeable", +] + +[[package]] +name = "text-size" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "timezone_provider" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9ba0000e9e73862f3e7ca1ff159e2ddf915c9d8bb11e38a7874760f445d993" +dependencies = [ + "tinystr", + "zerotrie", + "zerovec", + "zoneinfo64", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-id-start" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "v8" +version = "142.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f566072bd76b2631d0dca1d90a766c943863b1fd6b01312281dc919816de976d" +dependencies = [ + "bindgen", + "bitflags", + "fslock", + "gzip-header", + "home", + "miniz_oxide", + "paste", + "temporal_capi", + "which", +] + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm_dep_analyzer" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a10e6b67c951a84de7029487e0e0a496860dae49f6699edd279d5ff35b8fbf54" +dependencies = [ + "deno_error", + "thiserror", +] + +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix 0.38.44", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[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.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac060176f7020d62c3bcc1cdbcec619d54f48b07ad1963a3f80ce7a0c17755f" + +[[package]] +name = "zoneinfo64" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2e5597efbe7c421da8a7fd396b20b571704e787c21a272eecf35dfe9d386f0" +dependencies = [ + "calendrical_calculations", + "icu_locale_core", + "potential_utf", + "resb", + "serde", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ed51c07 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +resolver = "3" +members = [ + "nix-js", + "nix-js-macros" +] diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c5994ce --- /dev/null +++ b/flake.lock @@ -0,0 +1,66 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1767250179, + "narHash": "sha256-PnQdWvPZqHp+7yaHWDFX3NYSKaOy0fjkwpR+rIQC7AY=", + "owner": "nix-community", + "repo": "fenix", + "rev": "a3eaf682db8800962943a77ab77c0aae966f9825", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1767116409, + "narHash": "sha256-5vKw92l1GyTnjoLzEagJy5V5mDFck72LiQWZSOnSicw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "cad22e7d996aea55ecab064e84834289143e44a0", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "fenix": "fenix", + "nixpkgs": "nixpkgs" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1767191410, + "narHash": "sha256-cCZGjubgDWmstvFkS6eAw2qk2ihgWkycw55u2dtLd70=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "a9026e6d5068172bf5a0d52a260bb290961d1cb4", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..dfacd48 --- /dev/null +++ b/flake.nix @@ -0,0 +1,41 @@ +{ + nixConfig = { + extra-substituters = [ + "https://cache.garnix.io" + ]; + extra-trusted-public-keys = [ + "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" + ]; + }; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + fenix.url = "github:nix-community/fenix"; + fenix.inputs.nixpkgs.follows = "nixpkgs"; + }; + outputs = { nixpkgs, fenix, ... }: + let + forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed; + in + { + devShells = forAllSystems (system: + let pkgs = import nixpkgs { inherit system; config.allowUnfree = true; }; in + { + default = pkgs.mkShell { + packages = with pkgs; [ + (fenix.packages.${system}.stable.withComponents [ + "cargo" + "clippy" + "rust-src" + "rustc" + "rustfmt" + "rust-analyzer" + ]) + lldb + valgrind + claude-code + ]; + }; + } + ); + }; +} diff --git a/nix-js-macros/Cargo.toml b/nix-js-macros/Cargo.toml new file mode 100644 index 0000000..4004fff --- /dev/null +++ b/nix-js-macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nix-js-macros" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +convert_case = "0.8" +quote = "1.0" +proc-macro2 = "1.0" +syn = { version = "2.0", features = ["full"] } diff --git a/nix-js-macros/src/ir.rs b/nix-js-macros/src/ir.rs new file mode 100644 index 0000000..4b32a87 --- /dev/null +++ b/nix-js-macros/src/ir.rs @@ -0,0 +1,203 @@ +//! Implements the `ir!` procedural macro. +//! +//! This macro is designed to reduce the boilerplate associated with defining +//! an Intermediate Representation (IR) that follows a specific pattern. It generates: +//! 1. An enum representing the different kinds of IR nodes. +//! 2. Structs for each of the variants that have fields. +//! 3. `Ref` and `Mut` versions of the main enum for ergonomic pattern matching on references. +//! 4. `From` implementations to easily convert from a struct variant (e.g., `BinOp`) to the main enum (`Ir::BinOp`). +//! 5. A `To[IrName]` trait to provide a convenient `.to_ir()` method on the variant structs. + +use convert_case::{Case, Casing}; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{ + FieldsNamed, Ident, Token, Type, parenthesized, + parse::{Parse, ParseStream, Result}, + punctuated::Punctuated, + token, +}; + +/// Represents one of the variants passed to the `ir!` macro. +pub enum VariantInput { + /// A unit-like variant, e.g., `Arg`. + Unit(Ident), + /// A tuple-like variant with one unnamed field, e.g., `ExprRef(ExprId)`. + Tuple(Ident, Type), + /// A struct-like variant with named fields, e.g., `BinOp { lhs: ExprId, rhs: ExprId, kind: BinOpKind }`. + Struct(Ident, FieldsNamed), +} + +/// The top-level input for the `ir!` macro. +pub struct MacroInput { + /// The name of the main IR enum to be generated (e.g., `Ir`). + pub base_name: Ident, + /// The list of variants for the enum. + pub variants: Punctuated, +} + +impl Parse for VariantInput { + fn parse(input: ParseStream) -> Result { + let name: Ident = input.parse()?; + + if input.peek(token::Paren) { + // Parse a tuple-like variant: `Variant(Type)` + let content; + parenthesized!(content in input); + let ty: Type = content.parse()?; + + if !content.is_empty() { + return Err(content.error("Expected a single type inside parentheses")); + } + + Ok(VariantInput::Tuple(name, ty)) + } else if input.peek(token::Brace) { + // Parse a struct-like variant: `Variant { field: Type, ... }` + let fields: FieldsNamed = input.parse()?; + Ok(VariantInput::Struct(name, fields)) + } else { + // Parse a unit-like variant: `Variant` + Ok(VariantInput::Unit(name)) + } + } +} + +impl Parse for MacroInput { + fn parse(input: ParseStream) -> Result { + // The macro input is expected to be: `IrName, Variant1, Variant2, ...` + let base_name = input.parse()?; + input.parse::()?; + let variants = Punctuated::parse_terminated(input)?; + + Ok(MacroInput { + base_name, + variants, + }) + } +} + +/// The implementation of the `ir!` macro. +pub fn ir_impl(input: TokenStream) -> TokenStream { + let parsed_input = syn::parse_macro_input!(input as MacroInput); + + let base_name = &parsed_input.base_name; + let ref_name = format_ident!("{}Ref", base_name); + let mut_name = format_ident!("{}Mut", base_name); + let to_trait_name = format_ident!("To{}", base_name); + let to_trait_fn_name = format_ident!("to_{}", base_name.to_string().to_case(Case::Snake)); + + let mut enum_variants = Vec::new(); + let mut struct_defs = Vec::new(); + let mut ref_variants = Vec::new(); + let mut mut_variants = Vec::new(); + let mut as_ref_arms = Vec::new(); + let mut as_mut_arms = Vec::new(); + let mut from_impls = Vec::new(); + let mut to_trait_impls = Vec::new(); + + for variant in parsed_input.variants { + match variant { + VariantInput::Unit(name) => { + let inner_type = name.clone(); + enum_variants.push(quote! { #name(#inner_type) }); + ref_variants.push(quote! { #name(&'a #inner_type) }); + mut_variants.push(quote! { #name(&'a mut #inner_type) }); + as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) }); + as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) }); + from_impls.push(quote! { + impl From<#inner_type> for #base_name { + fn from(val: #inner_type) -> Self { #base_name::#name(val) } + } + }); + to_trait_impls.push(quote! { + impl #to_trait_name for #name { + fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) } + } + }); + } + VariantInput::Tuple(name, ty) => { + enum_variants.push(quote! { #name(#ty) }); + ref_variants.push(quote! { #name(&'a #ty) }); + mut_variants.push(quote! { #name(&'a mut #ty) }); + as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) }); + as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) }); + } + VariantInput::Struct(name, fields) => { + let inner_type = name.clone(); + struct_defs.push(quote! { + #[derive(Debug)] + pub struct #name #fields + }); + enum_variants.push(quote! { #name(#inner_type) }); + ref_variants.push(quote! { #name(&'a #inner_type) }); + mut_variants.push(quote! { #name(&'a mut #inner_type) }); + as_ref_arms.push(quote! { Self::#name(inner) => #ref_name::#name(inner) }); + as_mut_arms.push(quote! { Self::#name(inner) => #mut_name::#name(inner) }); + from_impls.push(quote! { + impl From<#inner_type> for #base_name { + fn from(val: #inner_type) -> Self { #base_name::#name(val) } + } + }); + to_trait_impls.push(quote! { + impl #to_trait_name for #name { + fn #to_trait_fn_name(self) -> #base_name { #base_name::from(self) } + } + }); + } + } + } + + // Assemble the final generated code. + let expanded = quote! { + /// The main IR enum, generated by the `ir!` macro. + #[derive(Debug, IsVariant, Unwrap, TryUnwrap)] + pub enum #base_name { + #( #enum_variants ),* + } + + // The struct definitions for the enum variants. + #( #struct_defs )* + + /// An immutable reference version of the IR enum. + #[derive(Debug, IsVariant, Unwrap, TryUnwrap)] + pub enum #ref_name<'a> { + #( #ref_variants ),* + } + + /// A mutable reference version of the IR enum. + #[derive(Debug, IsVariant, Unwrap, TryUnwrap)] + pub enum #mut_name<'a> { + #( #mut_variants ),* + } + + impl #base_name { + /// Converts a `&Ir` into a `IrRef`. + pub fn as_ref(&self) -> #ref_name<'_> { + match self { + #( #as_ref_arms ),* + } + } + + /// Converts a `&mut Ir` into a `IrMut`. + pub fn as_mut(&mut self) -> #mut_name<'_> { + match self { + #( #as_mut_arms ),* + } + } + } + + // `From` implementations for converting variant structs into the main enum. + #( #from_impls )* + + /// A trait for converting a variant struct into the main IR enum. + pub trait #to_trait_name { + /// Performs the conversion. + fn #to_trait_fn_name(self) -> #base_name; + } + + // Implement the `ToIr` trait for each variant struct. + #( #to_trait_impls )* + }; + + TokenStream::from(expanded) +} diff --git a/nix-js-macros/src/lib.rs b/nix-js-macros/src/lib.rs new file mode 100644 index 0000000..d2e4df4 --- /dev/null +++ b/nix-js-macros/src/lib.rs @@ -0,0 +1,13 @@ +//! This crate provides procedural macros for the nixjit project. +use proc_macro::TokenStream; + +mod ir; + +/// A procedural macro to reduce boilerplate when defining an Intermediate Representation (IR). +/// +/// It generates an enum for the IR, along with `Ref` and `Mut` variants, +/// `From` implementations, and a `ToIr` trait. +#[proc_macro] +pub fn ir(input: TokenStream) -> TokenStream { + ir::ir_impl(input) +} diff --git a/nix-js/Cargo.toml b/nix-js/Cargo.toml new file mode 100644 index 0000000..36b5ff9 --- /dev/null +++ b/nix-js/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "nix-js" +version = "0.1.0" +edition = "2024" + +[dependencies] +mimalloc = "0.1" + +# REPL +anyhow = "1.0" +rustyline = "14.0" + +regex = "1.11" +bumpalo = { version = "3.19", features = ["boxed"] } +hashbrown = "0.16" +derive_more = { version = "2", features = ["full"] } +thiserror = "2" +string-interner = "0.19" + +v8 = "142.2" +deno_core = "0.376" + +rnix = "0.12" + +nix-js-macros = { path = "../nix-js-macros" } diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs new file mode 100644 index 0000000..982bfed --- /dev/null +++ b/nix-js/src/codegen.rs @@ -0,0 +1,225 @@ +use crate::ir::*; + +pub trait Compile { + fn compile(&self, ctx: &Ctx) -> String; +} + +pub trait CodegenContext { + fn get_ir(&self, id: ExprId) -> &Ir; + fn get_sym(&self, id: SymId) -> &str; +} + +impl Compile for Ir { + fn compile(&self, ctx: &Ctx) -> String { + match self { + Ir::Const(Const { val }) => match val { + crate::value::Const::Null => "null".to_string(), + crate::value::Const::Int(val) => val.to_string(), + crate::value::Const::Float(val) => val.to_string(), + crate::value::Const::Bool(val) => val.to_string(), + }, + &Ir::If(If { cond, consq, alter }) => { + let cond = ctx.get_ir(cond).compile(ctx); + let consq = ctx.get_ir(consq).compile(ctx); + let alter = ctx.get_ir(alter).compile(ctx); + format!("({cond})?({consq}):({alter})") + } + Ir::BinOp(x) => x.compile(ctx), + Ir::UnOp(x) => x.compile(ctx), + Ir::Func(x) => x.compile(ctx), + Ir::AttrSet(x) => x.compile(ctx), + &Ir::Call(Call { func, arg }) => { + let func = ctx.get_ir(func).compile(ctx); + let arg = ctx.get_ir(arg).compile(ctx); + format!("NixRuntime.force({func})({arg})") + } + Ir::Arg(x) => format!("arg{}", x.0), + Ir::Let(x) => x.compile(ctx), + Ir::Select(x) => x.compile(ctx), + &Ir::Thunk(expr_id) => { + let inner = ctx.get_ir(expr_id).compile(ctx); + format!("NixRuntime.create_thunk(()=>({}))", inner) + } + &Ir::ExprRef(expr_id) => { + format!("expr{}", expr_id.0) + } + ir => todo!("{ir:?}"), + } + } +} + +impl Compile for BinOp { + fn compile(&self, ctx: &Ctx) -> String { + use BinOpKind::*; + let lhs = ctx.get_ir(self.lhs).compile(ctx); + let rhs = ctx.get_ir(self.rhs).compile(ctx); + match self.kind { + Add => format!("NixRuntime.op.add({},{})", lhs, rhs), + Sub => format!("NixRuntime.op.sub({},{})", lhs, rhs), + Mul => format!("NixRuntime.op.mul({},{})", lhs, rhs), + Div => format!("NixRuntime.op.div({},{})", lhs, rhs), + Eq => format!("NixRuntime.op.eq({},{})", lhs, rhs), + Neq => format!("NixRuntime.op.neq({},{})", lhs, rhs), + Lt => format!("NixRuntime.op.lt({},{})", lhs, rhs), + Gt => format!("NixRuntime.op.gt({},{})", lhs, rhs), + Leq => format!("NixRuntime.op.lte({},{})", lhs, rhs), + Geq => format!("NixRuntime.op.gte({},{})", lhs, rhs), + And => format!("NixRuntime.op.band({},{})", lhs, rhs), + Or => format!("NixRuntime.op.bor({},{})", lhs, rhs), + Impl => format!("NixRuntime.op.bor(NixRuntime.op.bnot({}),{})", lhs, rhs), + _ => todo!("BinOpKind::{:?}", self.kind), + } + } +} + +impl Compile for UnOp { + fn compile(&self, ctx: &Ctx) -> String { + use UnOpKind::*; + let rhs = ctx.get_ir(self.rhs).compile(ctx); + match self.kind { + Neg => format!("NixRuntime.op.sub(0,{rhs})"), + Not => format!("NixRuntime.op.bnot({rhs})") + } + } +} + + +impl Compile for Func { + fn compile(&self, ctx: &Ctx) -> String { + let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().0; + let body = ctx.get_ir(self.body).compile(ctx); + + // Generate parameter validation code + let param_check = self.generate_param_check(ctx); + + if param_check.is_empty() { + // Simple function without parameter validation + format!("arg{id}=>({body})") + } else { + // Function with parameter validation (use block statement, not object literal) + format!("arg{id}=>{{{}return {}}}", param_check, body) + } + } +} + +impl Func { + fn generate_param_check(&self, ctx: &Ctx) -> String { + let has_checks = self.param.required.is_some() || self.param.allowed.is_some(); + + if !has_checks { + return String::new(); + } + + let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().0; + + // Build required parameter array + let required = if let Some(req) = &self.param.required { + let keys: Vec<_> = req + .iter() + .map(|&sym| format!("\"{}\"", ctx.get_sym(sym))) + .collect(); + format!("[{}]", keys.join(",")) + } else { + "null".to_string() + }; + + // Build allowed parameter array + let allowed = if let Some(allow) = &self.param.allowed { + let keys: Vec<_> = allow + .iter() + .map(|&sym| format!("\"{}\"", ctx.get_sym(sym))) + .collect(); + format!("[{}]", keys.join(",")) + } else { + "null".to_string() + }; + + // Call NixRuntime.validate_params and store the result + format!("NixRuntime.validate_params(arg{},{},{});", id, required, allowed) + } +} + +impl Compile for Let { + fn compile(&self, ctx: &Ctx) -> String { + let declarations: Vec = self + .bindings + .iter() + .map(|&expr| format!("let expr{}", expr.0)) + .collect(); + + let assignments: Vec = self + .bindings + .iter() + .map(|&expr| { + let value = ctx.get_ir(expr).compile(ctx); + format!("expr{}={}", expr.0, value) + }) + .collect(); + + let body = ctx.get_ir(self.body).compile(ctx); + + format!( + "(()=>{{{}; {}; return {}}})()", + declarations.join(";"), + assignments.join(";"), + body + ) + } +} + +impl Compile for Select { + fn compile(&self, ctx: &Ctx) -> String { + let expr = ctx.get_ir(self.expr).compile(ctx); + + let mut result = expr; + let attr_count = self.attrpath.len(); + + for (i, attr) in self.attrpath.iter().enumerate() { + let is_last = i == attr_count - 1; + let has_default = self.default.is_some() && is_last; + + result = match attr { + Attr::Str(sym) => { + let key = ctx.get_sym(*sym); + if has_default { + let default_val = ctx.get_ir(self.default.unwrap()).compile(ctx); + format!("NixRuntime.select_with_default({}, \"{}\", {})", result, key, default_val) + } else { + format!("NixRuntime.select({}, \"{}\")", result, key) + } + } + Attr::Dynamic(expr_id) => { + let key = ctx.get_ir(*expr_id).compile(ctx); + if has_default { + let default_val = ctx.get_ir(self.default.unwrap()).compile(ctx); + format!("NixRuntime.select_with_default({}, {}, {})", result, key, default_val) + } else { + format!("NixRuntime.select({}, {})", result, key) + } + } + }; + } + + result + } +} + +impl Compile for AttrSet { + fn compile(&self, ctx: &Ctx) -> String { + let mut attrs = Vec::new(); + + for (&sym, &expr) in &self.stcs { + let key = ctx.get_sym(sym); + let value = ctx.get_ir(expr).compile(ctx); + attrs.push(format!("\"{}\": {}", key, value)); + } + + for (key_expr, value_expr) in &self.dyns { + let key = ctx.get_ir(*key_expr).compile(ctx); + let value = ctx.get_ir(*value_expr).compile(ctx); + attrs.push(format!("[{}]: {}", key, value)); + } + + format!("{{{}}}", attrs.join(", ")) + } +} diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs new file mode 100644 index 0000000..9aedd85 --- /dev/null +++ b/nix-js/src/context.rs @@ -0,0 +1,191 @@ +use std::ptr::NonNull; + +use hashbrown::HashMap; +use string_interner::DefaultStringInterner; + +use crate::codegen::{CodegenContext, Compile}; +use crate::error::{Error, Result}; +use crate::ir::{DowngradeContext, ExprId, Ir, SymId}; +use crate::value::Value; + +use downgrade::DowngradeCtx; + +mod downgrade; + +pub struct Context { + irs: Vec, + symbols: DefaultStringInterner, + global: NonNull>, +} + +impl Drop for Context { + fn drop(&mut self) { + unsafe { + drop(Box::from_raw(self.global.as_ptr())); + } + } +} + +impl Default for Context { + fn default() -> Self { + Self { + symbols: DefaultStringInterner::new(), + irs: Vec::new(), + global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(HashMap::new()))) }, + } + } +} + +impl Context { + pub fn new() -> Self { + Self::default() + } + + pub fn downgrade_ctx<'a>(&'a mut self) -> DowngradeCtx<'a> { + // SAFETY: `global` is readonly + let global_ref = unsafe { self.global.as_ref() }; + DowngradeCtx::new(self, global_ref) + } + + pub fn eval(&mut self, expr: &str) -> Result { + let root = rnix::Root::parse(expr); + if !root.errors().is_empty() { + return Err(Error::parse_error(root.errors().iter().fold( + String::new(), + |mut acc, err| { + acc.push_str(&err.to_string()); + acc.push_str("; "); + acc + }, + ))); + } + let root = self + .downgrade_ctx() + .downgrade(root.tree().expr().unwrap())?; + let code = self.get_ir(root).compile(self); + let code = format!("NixRuntime.force({})", code); + println!("[DEBUG] generated code: {}", &code); + crate::runtime::run(&code) + } +} + +impl CodegenContext for Context { + fn get_ir(&self, id: ExprId) -> &Ir { + self.irs.get(id.0).unwrap() + } + + fn get_sym(&self, id: SymId) -> &str { + self.symbols.resolve(id).unwrap() + } +} + +#[cfg(test)] +mod test { + use crate::value::Const; + use super::*; + + #[test] + fn basic_eval() { + assert_eq!( + Context::new().eval("1 + 1").unwrap(), + Value::Const(Const::Int(2)) + ); + assert_eq!( + Context::new().eval("(x: x) 1").unwrap(), + Value::Const(Const::Int(1)) + ); + assert_eq!( + Context::new().eval("(x: y: x - y) 2 1").unwrap(), + Value::Const(Const::Int(1)) + ); + assert_eq!( + Context::new().eval("rec { b = a; a = 1; }.b").unwrap(), + Value::Const(Const::Int(1)) + ); + assert_eq!( + Context::new().eval("let b = a; a = 1; in b").unwrap(), + Value::Const(Const::Int(1)) + ); + assert_eq!( + Context::new().eval("let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30").unwrap(), + Value::Const(Const::Int(832040)) + ); + assert_eq!( + Context::new() + .eval("((f: let x = f x; in x)(self: { x = 1; y = self.x + 1; })).y") + .unwrap(), + Value::Const(Const::Int(2)) + ); + } + + #[test] + fn test_param_check_required() { + // Test function with required parameters + assert_eq!( + Context::new().eval("({ a, b }: a + b) { a = 1; b = 2; }").unwrap(), + Value::Const(Const::Int(3)) + ); + + // Test missing required parameter should fail + let result = Context::new().eval("({ a, b }: a + b) { a = 1; }"); + assert!(result.is_err()); + + // Test all required parameters present + assert_eq!( + Context::new().eval("({ x, y, z }: x + y + z) { x = 1; y = 2; z = 3; }").unwrap(), + Value::Const(Const::Int(6)) + ); + } + + #[test] + fn test_param_check_allowed() { + // Test function without ellipsis - should reject unexpected arguments + let result = Context::new().eval("({ a, b }: a + b) { a = 1; b = 2; c = 3; }"); + assert!(result.is_err()); + + // Test function with ellipsis - should accept extra arguments + assert_eq!( + Context::new().eval("({ a, b, ... }: a + b) { a = 1; b = 2; c = 3; }").unwrap(), + Value::Const(Const::Int(3)) + ); + } + + #[test] + fn test_param_check_with_default() { + // Test function with default parameters + assert_eq!( + Context::new().eval("({ a, b ? 5 }: a + b) { a = 1; }").unwrap(), + Value::Const(Const::Int(6)) + ); + + // Test overriding default parameter + assert_eq!( + Context::new().eval("({ a, b ? 5 }: a + b) { a = 1; b = 10; }").unwrap(), + Value::Const(Const::Int(11)) + ); + } + + #[test] + fn test_param_check_with_alias() { + // Test function with @ pattern (alias) + assert_eq!( + Context::new().eval("(args@{ a, b }: args.a + args.b) { a = 1; b = 2; }").unwrap(), + Value::Const(Const::Int(3)) + ); + } + + #[test] + fn test_simple_param_no_check() { + // Test simple parameter (no pattern) should not have validation + assert_eq!( + Context::new().eval("(x: x.a + x.b) { a = 1; b = 2; }").unwrap(), + Value::Const(Const::Int(3)) + ); + + // Simple parameter accepts any argument + assert_eq!( + Context::new().eval("(x: x) 42").unwrap(), + Value::Const(Const::Int(42)) + ); + } +} diff --git a/nix-js/src/context/downgrade.rs b/nix-js/src/context/downgrade.rs new file mode 100644 index 0000000..f120417 --- /dev/null +++ b/nix-js/src/context/downgrade.rs @@ -0,0 +1,164 @@ +use hashbrown::HashMap; + +use crate::error::{Error, Result}; +use crate::ir::{ArgId, Downgrade, DowngradeContext, ExprId, Ir, SymId, ToIr}; + +use super::Context; + +enum Scope<'ctx> { + Global(&'ctx HashMap), + Let(HashMap), + Param(SymId, ExprId), + With(ExprId), +} + +struct ScopeGuard<'a, 'ctx> { + ctx: &'a mut DowngradeCtx<'ctx>, +} + +impl<'a, 'ctx> Drop for ScopeGuard<'a, 'ctx> { + fn drop(&mut self) { + self.ctx.scopes.pop(); + } +} + +impl<'a, 'ctx> ScopeGuard<'a, 'ctx> { + fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx> { + self.ctx + } +} + +pub struct DowngradeCtx<'ctx> { + ctx: &'ctx mut Context, + irs: Vec>, + scopes: Vec>, + arg_id: usize, +} + +impl<'ctx> DowngradeCtx<'ctx> { + pub fn new(ctx: &'ctx mut Context, global: &'ctx HashMap) -> Self { + Self { + scopes: vec![Scope::Global(global)], + irs: vec![], + arg_id: 0, + ctx, + } + } +} + +impl DowngradeContext for DowngradeCtx<'_> { + fn new_expr(&mut self, expr: Ir) -> ExprId { + self.irs.push(Some(expr)); + ExprId(self.ctx.irs.len() + self.irs.len() - 1) + } + + fn new_arg(&mut self) -> ExprId { + self.irs.push(Some(Ir::Arg(ArgId(self.arg_id)))); + self.arg_id += 1; + ExprId(self.ctx.irs.len() + self.irs.len() - 1) + } + + fn new_sym(&mut self, sym: String) -> SymId { + self.ctx.symbols.get_or_intern(sym) + } + + fn get_sym(&self, id: SymId) -> &str { + self.ctx.symbols.resolve(id).unwrap() + } + + fn lookup(&mut self, sym: SymId) -> Result { + for scope in self.scopes.iter().rev() { + match scope { + &Scope::Global(global_scope) => { + if let Some(&expr) = global_scope.get(&sym) { + return Ok(expr); + } + } + Scope::Let(let_scope) => { + if let Some(&expr) = let_scope.get(&sym) { + // Wrap in ExprRef to reference the binding instead of recompiling + return Ok(self.new_expr(Ir::ExprRef(expr))); + } + } + &Scope::Param(param_sym, expr) => { + if param_sym == sym { + return Ok(expr); + } + } + &Scope::With(_) => (), + } + } + + let namespaces: Vec = self + .scopes + .iter() + .filter_map(|scope| { + if let &Scope::With(namespace) = scope { + Some(namespace) + } else { + None + } + }) + .collect(); + let mut result = None; + for namespace in namespaces { + use crate::ir::{Attr, Select}; + let select = Select { + expr: namespace, + attrpath: vec![Attr::Str(sym)], + default: result, // Link to outer With or None + }; + result = Some(self.new_expr(select.to_ir())); + } + result.ok_or_else(|| Error::downgrade_error(format!("'{}' not found", self.get_sym(sym)))) + } + + fn extract_expr(&mut self, id: ExprId) -> Ir { + self.irs.get_mut(id.0).unwrap().take().unwrap() + } + + fn replace_expr(&mut self, id: ExprId, expr: Ir) { + let _ = self.irs.get_mut(id.0).unwrap().insert(expr); + } + + #[allow(refining_impl_trait)] + fn reserve_slots(&mut self, slots: usize) -> impl Iterator + Clone + use<> { + self.irs.extend(std::iter::repeat_with(|| None).take(slots)); + (self.irs.len() - slots..self.irs.len()).map(ExprId) + } + + fn downgrade(mut self, root: rnix::ast::Expr) -> Result { + let root = root.downgrade(&mut self)?; + self.ctx + .irs + .extend(self.irs.into_iter().map(Option::unwrap)); + Ok(root) + } + + fn with_let_scope(&mut self, bindings: HashMap, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + self.scopes.push(Scope::Let(bindings)); + let mut guard = ScopeGuard { ctx: self }; + f(guard.as_ctx()) + } + + fn with_param_scope(&mut self, param: SymId, arg: ExprId, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + self.scopes.push(Scope::Param(param, arg)); + let mut guard = ScopeGuard { ctx: self }; + f(guard.as_ctx()) + } + + fn with_with_scope(&mut self, namespace: ExprId, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + self.scopes.push(Scope::With(namespace)); + let mut guard = ScopeGuard { ctx: self }; + f(guard.as_ctx()) + } +} diff --git a/nix-js/src/error.rs b/nix-js/src/error.rs new file mode 100644 index 0000000..d5e9972 --- /dev/null +++ b/nix-js/src/error.rs @@ -0,0 +1,124 @@ +use std::rc::Rc; +use thiserror::Error; + +pub type Result = core::result::Result; + +#[derive(Error, Debug)] +pub enum ErrorKind { + #[error("error occurred during parse stage: {0}")] + ParseError(String), + #[error("error occurred during downgrade stage: {0}")] + DowngradeError(String), + #[error("error occurred during evaluation stage: {0}")] + EvalError(String), + #[error("{0}")] + Catchable(String), + #[error("an unknown or unexpected error occurred")] + Unknown, +} + +#[derive(Debug)] +pub struct Error { + pub kind: ErrorKind, + pub span: Option, + pub source: Option>, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Basic display + write!(f, "{}", self.kind)?; + + // If we have source and span, print context + if let (Some(source), Some(span)) = (&self.source, self.span) { + let start_byte = usize::from(span.start()); + let end_byte = usize::from(span.end()); + + if start_byte > source.len() || end_byte > source.len() { + return Ok(()); // Span is out of bounds + } + + let mut start_line = 1; + let mut start_col = 1usize; + let mut line_start_byte = 0; + for (i, c) in source.char_indices() { + if i >= start_byte { + break; + } + if c == '\n' { + start_line += 1; + start_col = 1; + line_start_byte = i + 1; + } else { + start_col += 1; + } + } + + let line_end_byte = source[line_start_byte..] + .find('\n') + .map(|i| line_start_byte + i) + .unwrap_or(source.len()); + + let line_str = &source[line_start_byte..line_end_byte]; + + let underline_len = if end_byte > start_byte { + end_byte - start_byte + } else { + 1 + }; + + write!(f, "\n --> {}:{}", start_line, start_col)?; + write!(f, "\n |\n")?; + writeln!(f, "{:4} | {}", start_line, line_str)?; + write!( + f, + " | {}{}", + " ".repeat(start_col.saturating_sub(1)), + "^".repeat(underline_len) + )?; + } + Ok(()) + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.kind) + } +} + +impl Error { + pub fn new(kind: ErrorKind) -> Self { + Self { + kind, + span: None, + source: None, + } + } + + pub fn with_span(mut self, span: rnix::TextRange) -> Self { + self.span = Some(span); + self + } + + pub fn with_source(mut self, source: Rc) -> Self { + self.source = Some(source); + self + } + + pub fn parse_error(msg: String) -> Self { + Self::new(ErrorKind::ParseError(msg)) + } + pub fn downgrade_error(msg: String) -> Self { + Self::new(ErrorKind::DowngradeError(msg)) + } + pub fn eval_error(msg: String) -> Self { + Self::new(ErrorKind::EvalError(msg)) + } + pub fn catchable(msg: String) -> Self { + Self::new(ErrorKind::Catchable(msg)) + } + pub fn unknown() -> Self { + Self::new(ErrorKind::Unknown) + } +} diff --git a/nix-js/src/ir.rs b/nix-js/src/ir.rs new file mode 100644 index 0000000..87cfe59 --- /dev/null +++ b/nix-js/src/ir.rs @@ -0,0 +1,393 @@ +use derive_more::{IsVariant, TryUnwrap, Unwrap}; +use hashbrown::{HashMap, HashSet}; +use rnix::ast; +use string_interner::symbol::SymbolU32; + +use crate::error::{Error, Result}; +use crate::value::Const as PubConst; +use crate::value::format_symbol; +use nix_js_macros::ir; + +mod downgrade; +mod utils; +use utils::*; + +pub use downgrade::Downgrade; + +pub trait DowngradeContext { + fn downgrade(self, expr: rnix::ast::Expr) -> Result; + + fn new_expr(&mut self, expr: Ir) -> ExprId; + fn new_arg(&mut self) -> ExprId; + + fn new_sym(&mut self, sym: String) -> SymId; + fn get_sym(&self, id: SymId) -> &str; + fn lookup(&mut self, sym: SymId) -> Result; + + fn extract_expr(&mut self, id: ExprId) -> Ir; + fn replace_expr(&mut self, id: ExprId, expr: Ir); + fn reserve_slots(&mut self, slots: usize) -> impl Iterator + Clone + use; + + fn with_param_scope(&mut self, param: SymId, arg: ExprId, f: F) -> R + where + F: FnOnce(&mut Self) -> R; + fn with_let_scope(&mut self, bindings: HashMap, f: F) -> R + where + F: FnOnce(&mut Self) -> R; + fn with_with_scope(&mut self, namespace: ExprId, f: F) -> R + where + F: FnOnce(&mut Self) -> R; +} + +ir! { + Ir, + + AttrSet, + List, + HasAttr, + BinOp, + UnOp, + Select, + If, + Call, + With, + Assert, + ConcatStrings, + Const, + Str, + Path, + Func, + Let, + Arg(ArgId), + PrimOp(PrimOpId), + ExprRef(ExprId), + Thunk(ExprId), +} + +impl AttrSet { + fn _insert( + &mut self, + mut path: impl Iterator, + name: Attr, + value: ExprId, + ctx: &mut impl DowngradeContext, + ) -> Result<()> { + if let Some(attr) = path.next() { + // If the path is not yet exhausted, we need to recurse deeper. + match attr { + Attr::Str(ident) => { + // If the next attribute is a static string. + if let Some(&id) = self.stcs.get(&ident) { + // If a sub-attrset already exists, recurse into it. + let mut ir = ctx.extract_expr(id); + let result = ir + .as_mut() + .try_unwrap_attr_set() + .map_err(|_| { + // This path segment exists but is not an attrset, which is an error. + Error::downgrade_error(format!( + "attribute '{}' already defined but is not an attribute set", + format_symbol(ctx.get_sym(ident)) + )) + }) + .and_then(|attrs| attrs._insert(path, name, value, ctx)); + ctx.replace_expr(id, ir); + result?; + } else { + // Create a new sub-attrset because this path doesn't exist yet. + let mut attrs = AttrSet::default(); + attrs._insert(path, name, value, ctx)?; + let attrs = ctx.new_expr(attrs.to_ir()); + self.stcs.insert(ident, attrs); + } + Ok(()) + } + Attr::Dynamic(dynamic) => { + // If the next attribute is a dynamic expression, we must create a new sub-attrset. + // We cannot merge with existing dynamic attributes at this stage. + let mut attrs = AttrSet::default(); + attrs._insert(path, name, value, ctx)?; + self.dyns.push((dynamic, ctx.new_expr(attrs.to_ir()))); + Ok(()) + } + } + } else { + // This is the final attribute in the path, so insert the value here. + match name { + Attr::Str(ident) => { + if self.stcs.insert(ident, value).is_some() { + return Err(Error::downgrade_error(format!( + "attribute '{}' already defined", + format_symbol(ctx.get_sym(ident)) + ))); + } + } + Attr::Dynamic(dynamic) => { + self.dyns.push((dynamic, value)); + } + } + Ok(()) + } + } + + fn insert( + &mut self, + path: Vec, + value: ExprId, + ctx: &mut impl DowngradeContext, + ) -> Result<()> { + let mut path = path.into_iter(); + // The last part of the path is the name of the attribute to be inserted. + let name = path.next_back().unwrap(); + self._insert(path, name, value, ctx) + } +} + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ExprId(pub usize); + +pub type SymId = SymbolU32; + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct PrimOpId(pub usize); + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ArgId(pub usize); + +/// Represents a Nix attribute set. +#[derive(Debug, Default)] +pub struct AttrSet { + /// Statically known attributes (key is a string). + pub stcs: HashMap, + /// Dynamically computed attributes, where both the key and value are expressions. + pub dyns: Vec<(ExprId, ExprId)>, +} + +/// Represents a key in an attribute path. +#[derive(Debug, TryUnwrap)] +pub enum Attr { + /// A dynamic attribute key, which is an expression that must evaluate to a string. + /// Example: `attrs.${key}` + Dynamic(ExprId), + /// A static attribute key. + /// Example: `attrs.key` + Str(SymId), +} + +/// Represents a Nix list. +#[derive(Debug)] +pub struct List { + /// The expressions that are elements of the list. + pub items: Vec, +} + +/// Represents a "has attribute" check (`?` operator). +#[derive(Debug)] +pub struct HasAttr { + /// The expression to check for the attribute (the left-hand side). + pub lhs: ExprId, + /// The attribute path to look for (the right-hand side). + pub rhs: Vec, +} + +/// Represents a binary operation. +#[derive(Debug)] +pub struct BinOp { + pub lhs: ExprId, + pub rhs: ExprId, + pub kind: BinOpKind, +} + +/// The kinds of binary operations supported in Nix. +#[derive(Clone, Debug)] +pub enum BinOpKind { + // Arithmetic + Add, + Sub, + Div, + Mul, + + // Comparison + Eq, + Neq, + Lt, + Gt, + Leq, + Geq, + + // Logical + And, + Or, + Impl, + + // Set/String/Path operations + Con, // List concatenation (`++`) + Upd, // AttrSet update (`//`) + + // Not standard, but part of rnix AST + PipeL, + PipeR, +} + +impl From for BinOpKind { + fn from(op: ast::BinOpKind) -> Self { + use BinOpKind::*; + use ast::BinOpKind as kind; + match op { + kind::Concat => Con, + kind::Update => Upd, + kind::Add => Add, + kind::Sub => Sub, + kind::Mul => Mul, + kind::Div => Div, + kind::And => And, + kind::Equal => Eq, + kind::Implication => Impl, + kind::Less => Lt, + kind::LessOrEq => Leq, + kind::More => Gt, + kind::MoreOrEq => Geq, + kind::NotEqual => Neq, + kind::Or => Or, + kind::PipeLeft => PipeL, + kind::PipeRight => PipeR, + } + } +} + +/// Represents a unary operation. +#[derive(Debug)] +pub struct UnOp { + pub rhs: ExprId, + pub kind: UnOpKind, +} + +/// The kinds of unary operations. +#[derive(Clone, Debug)] +pub enum UnOpKind { + Neg, // Negation (`-`) + Not, // Logical not (`!`) +} + +impl From for UnOpKind { + fn from(value: ast::UnaryOpKind) -> Self { + match value { + ast::UnaryOpKind::Invert => UnOpKind::Not, + ast::UnaryOpKind::Negate => UnOpKind::Neg, + } + } +} + +/// Represents an attribute selection from an attribute set. +#[derive(Debug)] +pub struct Select { + /// The expression that should evaluate to an attribute set. + pub expr: ExprId, + /// The path of attributes to select. + pub attrpath: Vec, + /// An optional default value to return if the selection fails. + pub default: Option, +} + +/// Represents an `if-then-else` expression. +#[derive(Debug)] +pub struct If { + pub cond: ExprId, + pub consq: ExprId, // Consequence (then branch) + pub alter: ExprId, // Alternative (else branch) +} + +/// Represents a function value (a lambda). +#[derive(Debug)] +pub struct Func { + /// The body of the function + pub body: ExprId, + /// The parameter specification for the function. + pub param: Param, + + pub arg: ExprId, +} + +/// Represents a `let ... in ...` expression. +#[derive(Debug)] +pub struct Let { + /// The bindings in the let expression. + pub bindings: Vec, + /// The body expression evaluated in the scope of the bindings. + pub body: ExprId, +} + +/// Describes the parameters of a function. +#[derive(Debug)] +pub struct Param { + /// The name of the argument if it's a simple identifier (e.g., `x: ...`). + /// Also used for the alias in a pattern (e.g., `args @ { ... }`). + pub ident: Option, + /// The set of required parameter names for a pattern-matching function. + pub required: Option>, + /// The set of all allowed parameter names for a non-ellipsis pattern-matching function. + /// If `None`, any attribute is allowed (ellipsis `...` is present). + pub allowed: Option>, +} + +/// Represents a function call. +#[derive(Debug)] +pub struct Call { + /// The expression that evaluates to the function to be called. + pub func: ExprId, + pub arg: ExprId, +} + +/// Represents a `with` expression. +#[derive(Debug)] +pub struct With { + /// The namespace to bring into scope. + pub namespace: ExprId, + /// The expression to be evaluated within the new scope. + pub expr: ExprId, +} + +/// Represents an `assert` expression. +#[derive(Debug)] +pub struct Assert { + /// The condition to assert. + pub assertion: ExprId, + /// The expression to return if the assertion is true. + pub expr: ExprId, +} + +/// Represents the concatenation of multiple string expressions. +/// This is typically the result of downgrading an interpolated string. +#[derive(Debug)] +pub struct ConcatStrings { + pub parts: Vec, +} + +/// Represents a constant value (e.g., integer, float, boolean, null). +#[derive(Clone, Copy, Debug)] +pub struct Const { + pub val: PubConst, +} + +impl> From for Const { + fn from(value: T) -> Self { + Self { val: value.into() } + } +} + +/// Represents a simple, non-interpolated string literal. +#[derive(Debug)] +pub struct Str { + pub val: String, +} + +/// Represents a path literal. +#[derive(Debug)] +pub struct Path { + /// The expression that evaluates to the string content of the path. + /// This can be a simple `Str` or a `ConcatStrings` for interpolated paths. + pub expr: ExprId, +} diff --git a/nix-js/src/ir/downgrade.rs b/nix-js/src/ir/downgrade.rs new file mode 100644 index 0000000..e838db3 --- /dev/null +++ b/nix-js/src/ir/downgrade.rs @@ -0,0 +1,389 @@ +use rnix::ast::{self, Expr, HasEntry}; + +use crate::error::{Error, Result}; + +use super::*; + +pub trait Downgrade { + fn downgrade(self, ctx: &mut Ctx) -> Result; +} + +impl Downgrade for Expr { + fn downgrade(self, ctx: &mut Ctx) -> Result { + use Expr::*; + match self { + Apply(apply) => apply.downgrade(ctx), + Assert(assert) => assert.downgrade(ctx), + Error(error) => Err(self::Error::downgrade_error(error.to_string())), + IfElse(ifelse) => ifelse.downgrade(ctx), + Select(select) => select.downgrade(ctx), + Str(str) => str.downgrade(ctx), + Path(path) => path.downgrade(ctx), + Literal(lit) => lit.downgrade(ctx), + Lambda(lambda) => lambda.downgrade(ctx), + LegacyLet(let_) => let_.downgrade(ctx), + LetIn(letin) => letin.downgrade(ctx), + List(list) => list.downgrade(ctx), + BinOp(op) => op.downgrade(ctx), + AttrSet(attrs) => attrs.downgrade(ctx), + UnaryOp(op) => op.downgrade(ctx), + Ident(ident) => ident.downgrade(ctx), + With(with) => with.downgrade(ctx), + HasAttr(has) => has.downgrade(ctx), + Paren(paren) => paren.expr().unwrap().downgrade(ctx), + Root(root) => root.expr().unwrap().downgrade(ctx), + } + } +} + +impl Downgrade for ast::Assert { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let assertion = self.condition().unwrap().downgrade(ctx)?; + let expr = self.body().unwrap().downgrade(ctx)?; + Ok(ctx.new_expr(Assert { assertion, expr }.to_ir())) + } +} + +impl Downgrade for ast::IfElse { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let cond = self.condition().unwrap().downgrade(ctx)?; + let consq = self.body().unwrap().downgrade(ctx)?; + let alter = self.else_body().unwrap().downgrade(ctx)?; + Ok(ctx.new_expr(If { cond, consq, alter }.to_ir())) + } +} + +impl Downgrade for ast::Path { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let parts = self + .parts() + .map(|part| match part { + ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr( + Str { + val: lit.to_string(), + } + .to_ir(), + )), + ast::InterpolPart::Interpolation(interpol) => { + interpol.expr().unwrap().downgrade(ctx) + } + }) + .collect::>>()?; + let expr = if parts.len() == 1 { + parts.into_iter().next().unwrap() + } else { + ctx.new_expr(ConcatStrings { parts }.to_ir()) + }; + Ok(ctx.new_expr(Path { expr }.to_ir())) + } +} + +impl Downgrade for ast::Str { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let parts = self + .normalized_parts() + .into_iter() + .map(|part| match part { + ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit }.to_ir())), + ast::InterpolPart::Interpolation(interpol) => { + interpol.expr().unwrap().downgrade(ctx) + } + }) + .collect::>>()?; + Ok(if parts.len() == 1 { + parts.into_iter().next().unwrap() + } else { + ctx.new_expr(ConcatStrings { parts }.to_ir()) + }) + } +} + +impl Downgrade for ast::Literal { + fn downgrade(self, ctx: &mut Ctx) -> Result { + Ok(ctx.new_expr(match self.kind() { + ast::LiteralKind::Integer(int) => Const::from(int.value().unwrap()).to_ir(), + ast::LiteralKind::Float(float) => Const::from(float.value().unwrap()).to_ir(), + ast::LiteralKind::Uri(uri) => Str { + val: uri.to_string(), + } + .to_ir(), + })) + } +} + +impl Downgrade for ast::Ident { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let sym = self.ident_token().unwrap().to_string(); + let sym = ctx.new_sym(sym); + ctx.lookup(sym) + } +} + +impl Downgrade for ast::AttrSet { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let rec = self.rec_token().is_some(); + + if !rec { + let attrs = downgrade_attrs(self, ctx)?; + return Ok(ctx.new_expr(attrs.to_ir())); + } + + // rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; } + + let entries: Vec<_> = self.entries().collect(); + + let (bindings, body) = downgrade_let_bindings(entries, ctx, |ctx, binding_keys| { + // Create plain attrset as body with inherit + let mut attrs = AttrSet { + stcs: HashMap::new(), + dyns: Vec::new(), + }; + + for sym in binding_keys { + let expr = ctx.lookup(*sym)?; + attrs.stcs.insert(*sym, expr); + } + + Ok(ctx.new_expr(attrs.to_ir())) + })?; + + // Create Let expression + Ok(ctx.new_expr(Let { bindings, body }.to_ir())) + } +} + +/// Downgrades a list. +impl Downgrade for ast::List { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let items = self + .items() + .map(|item| maybe_thunk(item, ctx)) + .collect::>()?; + Ok(ctx.new_expr(List { items }.to_ir())) + } +} + +/// Downgrades a binary operation. +impl Downgrade for ast::BinOp { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let lhs = self.lhs().unwrap().downgrade(ctx)?; + let rhs = self.rhs().unwrap().downgrade(ctx)?; + let kind = self.operator().unwrap().into(); + Ok(ctx.new_expr(BinOp { lhs, rhs, kind }.to_ir())) + } +} + +/// Downgrades a "has attribute" (`?`) expression. +impl Downgrade for ast::HasAttr { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let lhs = self.expr().unwrap().downgrade(ctx)?; + let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?; + Ok(ctx.new_expr(HasAttr { lhs, rhs }.to_ir())) + } +} + +/// Downgrades a unary operation. +impl Downgrade for ast::UnaryOp { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let rhs = self.expr().unwrap().downgrade(ctx)?; + let kind = self.operator().unwrap().into(); + Ok(ctx.new_expr(UnOp { rhs, kind }.to_ir())) + } +} + +/// Downgrades an attribute selection (`.`). +impl Downgrade for ast::Select { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let expr = self.expr().unwrap().downgrade(ctx)?; + let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?; + let default = if let Some(default) = self.default_expr() { + Some(default.downgrade(ctx)?) + } else { + None + }; + Ok(ctx.new_expr( + Select { + expr, + attrpath, + default, + } + .to_ir(), + )) + } +} + +/// Downgrades a `legacy let`, which is essentially a recursive attribute set. +/// The body of the `let` is accessed via `let.body`. +impl Downgrade for ast::LegacyLet { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let bindings = downgrade_static_attrs(self, ctx)?; + let binding_keys: Vec<_> = bindings.keys().copied().collect(); + + let attrset_expr = ctx.with_let_scope(bindings, |ctx| { + let mut attrs = AttrSet { + stcs: HashMap::new(), + dyns: Vec::new(), + }; + + for sym in binding_keys { + let expr = ctx.lookup(sym)?; + attrs.stcs.insert(sym, expr); + } + + Ok(ctx.new_expr(attrs.to_ir())) + })?; + + let body_sym = ctx.new_sym("body".to_string()); + let select = Select { + expr: attrset_expr, + attrpath: vec![Attr::Str(body_sym)], + default: None, + }; + + Ok(ctx.new_expr(select.to_ir())) + } +} + +/// Downgrades a `let ... in ...` expression. +impl Downgrade for ast::LetIn { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let entries: Vec<_> = self.entries().collect(); + let body_expr = self.body().unwrap(); + + let (bindings, body) = downgrade_let_bindings(entries, ctx, |ctx, _binding_keys| { + body_expr.downgrade(ctx) + })?; + + Ok(ctx.new_expr(Let { bindings, body }.to_ir())) + } +} + +/// Downgrades a `with` expression. +impl Downgrade for ast::With { + fn downgrade(self, ctx: &mut Ctx) -> Result { + // with namespace; expr + let namespace = self.namespace().unwrap().downgrade(ctx)?; + + // Downgrade body in With scope + let expr = ctx.with_with_scope(namespace, |ctx| self.body().unwrap().downgrade(ctx))?; + + Ok(expr) + } +} + +/// Downgrades a lambda (function) expression. +/// This involves desugaring pattern-matching arguments into `let` bindings. +impl Downgrade for ast::Lambda { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let arg = ctx.new_arg(); + + let ident; + let required; + let allowed; + let body; + + match self.param().unwrap() { + ast::Param::IdentParam(id) => { + // Simple case: `x: body` + let param_sym = ctx.new_sym(id.to_string()); + ident = Some(param_sym); + required = None; + allowed = None; + + // Downgrade body in Param scope + body = ctx + .with_param_scope(param_sym, arg, |ctx| self.body().unwrap().downgrade(ctx))?; + } + ast::Param::Pattern(pattern) => { + // Complex case: `{ a, b ? 2, ... }@args: body` + let alias = pattern + .pat_bind() + .map(|alias| ctx.new_sym(alias.ident().unwrap().to_string())); + ident = alias; + + let entries = pattern + .pat_entries() + .map(|entry| { + let ident = ctx.new_sym(entry.ident().unwrap().to_string()); + if entry.default().is_none() { + Ok((ident, None)) + } else { + entry + .default() + .unwrap() + .downgrade(ctx) + .map(|ok| (ident, Some(ok))) + } + }) + .collect::>>()?; + + required = Some( + entries + .iter() + .filter_map(|(k, d)| if d.is_none() { Some(*k) } else { None }) + .collect(), + ); + allowed = if pattern.ellipsis_token().is_some() { + None // `...` means any attribute is allowed. + } else { + Some(entries.iter().map(|(k, _)| *k).collect()) + }; + + // Desugar pattern matching in function arguments into a `let` expression. + // For example, `({ a, b ? 2 }): a + b` is desugared into: + // `arg: let a = arg.a; b = arg.b or 2; in a + b` + let mut bindings: HashMap<_, _> = entries + .into_iter() + .map(|(k, default)| { + // For each formal parameter, create a `Select` expression to extract it from the argument set. + ( + k, + ctx.new_expr( + Select { + expr: arg, + attrpath: vec![Attr::Str(k)], + default, + } + .to_ir(), + ), + ) + }) + .collect(); + + // If there's an alias (`... }@alias`), bind the alias name to the raw argument set. + if let Some(alias) = alias { + bindings.insert(alias, arg); + } + + // Downgrade body in Let scope and create Let expression + let bindings_vec: Vec = bindings.values().copied().collect(); + let inner_body = ctx.with_let_scope(bindings, |ctx| self.body().unwrap().downgrade(ctx))?; + + // Create Let expression to wrap the bindings + body = ctx.new_expr(Let { + bindings: bindings_vec, + body: inner_body, + }.to_ir()); + } + } + + let param = Param { + ident, + required, + allowed, + }; + // The function's body and parameters are now stored directly in the `Func` node. + Ok(ctx.new_expr(Func { body, param, arg }.to_ir())) + } +} + +/// Downgrades a function application. +/// In Nix, function application is left-associative, so `f a b` should be parsed as `((f a) b)`. +/// Each Apply node represents a single function call with one argument. +impl Downgrade for ast::Apply { + fn downgrade(self, ctx: &mut Ctx) -> Result { + let func = self.lambda().unwrap().downgrade(ctx)?; + let arg = maybe_thunk(self.argument().unwrap(), ctx)?; + Ok(ctx.new_expr(Call { func, arg }.to_ir())) + } +} diff --git a/nix-js/src/ir/utils.rs b/nix-js/src/ir/utils.rs new file mode 100644 index 0000000..341b364 --- /dev/null +++ b/nix-js/src/ir/utils.rs @@ -0,0 +1,303 @@ +use hashbrown::{HashMap, HashSet}; +use hashbrown::hash_map::Entry; +use rnix::ast; + +use crate::error::{Error, Result}; +use crate::ir::{Attr, AttrSet, ConcatStrings, ExprId, Ir, Select, Str, SymId}; +use crate::value::format_symbol; + +use super::*; + +pub fn maybe_thunk(mut expr: ast::Expr, ctx: &mut impl DowngradeContext) -> Result { + use ast::Expr::*; + let expr = loop { + expr = match expr { + Paren(paren) => paren.expr().unwrap(), + Root(root) => root.expr().unwrap(), + expr => break expr, + } + }; + match expr { + Error(error) => return Err(self::Error::downgrade_error(error.to_string())), + Ident(ident) => return ident.downgrade(ctx), + Literal(lit) => return lit.downgrade(ctx), + Str(str) => return str.downgrade(ctx), + Path(path) => return path.downgrade(ctx), + + _ => (), + } + let id = match expr { + Apply(apply) => apply.downgrade(ctx), + Assert(assert) => assert.downgrade(ctx), + IfElse(ifelse) => ifelse.downgrade(ctx), + Select(select) => select.downgrade(ctx), + Lambda(lambda) => lambda.downgrade(ctx), + LegacyLet(let_) => let_.downgrade(ctx), + LetIn(letin) => letin.downgrade(ctx), + List(list) => list.downgrade(ctx), + BinOp(op) => op.downgrade(ctx), + AttrSet(attrs) => attrs.downgrade(ctx), + UnaryOp(op) => op.downgrade(ctx), + With(with) => with.downgrade(ctx), + HasAttr(has) => has.downgrade(ctx), + + _ => unreachable!(), + }?; + Ok(ctx.new_expr(Ir::Thunk(id))) +} + +/// Downgrades the entries of an attribute set. +/// This handles `inherit` and `attrpath = value;` entries. +pub fn downgrade_attrs( + attrs: impl ast::HasEntry, + ctx: &mut impl DowngradeContext, +) -> Result { + let entries = attrs.entries(); + let mut attrs = AttrSet { + stcs: HashMap::new(), + dyns: Vec::new(), + }; + + for entry in entries { + match entry { + ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?, + ast::Entry::AttrpathValue(value) => downgrade_attrpathvalue(value, &mut attrs, ctx)?, + } + } + + Ok(attrs) +} + +/// Downgrades attribute set entries for a `let...in` expression. +/// This is a stricter version of `downgrade_attrs` that disallows dynamic attributes, +/// as `let` bindings must be statically known. +pub fn downgrade_static_attrs( + attrs: impl ast::HasEntry, + ctx: &mut impl DowngradeContext, +) -> Result> { + let entries = attrs.entries(); + let mut attrs = AttrSet { + stcs: HashMap::new(), + dyns: Vec::new(), + }; + + for entry in entries { + match entry { + ast::Entry::Inherit(inherit) => downgrade_inherit(inherit, &mut attrs.stcs, ctx)?, + ast::Entry::AttrpathValue(value) => { + downgrade_static_attrpathvalue(value, &mut attrs, ctx)? + } + } + } + + Ok(attrs.stcs) +} + +/// Downgrades an `inherit` statement. +/// `inherit (from) a b;` is translated into `a = from.a; b = from.b;`. +/// `inherit a b;` is translated into `a = a; b = b;` (i.e., bringing variables into scope). +pub fn downgrade_inherit( + inherit: ast::Inherit, + stcs: &mut HashMap, + ctx: &mut impl DowngradeContext, +) -> Result<()> { + // Downgrade the `from` expression if it exists. + let from = if let Some(from) = inherit.from() { + Some(from.expr().unwrap().downgrade(ctx)?) + } else { + None + }; + for attr in inherit.attrs() { + let ident = match downgrade_attr(attr, ctx)? { + Attr::Str(ident) => ident, + _ => { + // `inherit` does not allow dynamic attributes. + return Err(Error::downgrade_error( + "dynamic attributes not allowed in inherit".to_string(), + )); + } + }; + let expr = if let Some(expr) = from { + ctx.new_expr( + Select { + expr, + attrpath: vec![Attr::Str(ident)], + default: None, + } + .to_ir(), + ) + } else { + ctx.lookup(ident)? + }; + match stcs.entry(ident) { + Entry::Occupied(occupied) => { + return Err(Error::eval_error(format!( + "attribute '{}' already defined", + format_symbol(ctx.get_sym(*occupied.key())) + ))); + } + Entry::Vacant(vacant) => vacant.insert(expr), + }; + } + Ok(()) +} + +/// Downgrades a single attribute key (part of an attribute path). +/// An attribute can be a static identifier, an interpolated string, or a dynamic expression. +pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result { + use ast::Attr::*; + use ast::InterpolPart::*; + match attr { + Ident(ident) => Ok(Attr::Str(ctx.new_sym(ident.to_string()))), + Str(string) => { + let parts = string.normalized_parts(); + if parts.is_empty() { + Ok(Attr::Str(ctx.new_sym("".to_string()))) + } else if parts.len() == 1 { + // If the string has only one part, it's either a literal or a single interpolation. + match parts.into_iter().next().unwrap() { + Literal(ident) => Ok(Attr::Str(ctx.new_sym(ident))), + Interpolation(interpol) => { + Ok(Attr::Dynamic(interpol.expr().unwrap().downgrade(ctx)?)) + } + } + } else { + // If the string has multiple parts, it's an interpolated string that must be concatenated. + let parts = parts + .into_iter() + .map(|part| match part { + Literal(lit) => Ok(ctx.new_expr(self::Str { val: lit }.to_ir())), + Interpolation(interpol) => interpol.expr().unwrap().downgrade(ctx), + }) + .collect::>>()?; + Ok(Attr::Dynamic(ctx.new_expr(ConcatStrings { parts }.to_ir()))) + } + } + Dynamic(dynamic) => Ok(Attr::Dynamic(dynamic.expr().unwrap().downgrade(ctx)?)), + } +} + +/// Downgrades an attribute path (e.g., `a.b."${c}".d`) into a `Vec`. +pub fn downgrade_attrpath( + attrpath: ast::Attrpath, + ctx: &mut impl DowngradeContext, +) -> Result> { + attrpath + .attrs() + .map(|attr| downgrade_attr(attr, ctx)) + .collect::>>() +} + +/// Downgrades an `attrpath = value;` expression and inserts it into an `AttrSet`. +pub fn downgrade_attrpathvalue( + value: ast::AttrpathValue, + attrs: &mut AttrSet, + ctx: &mut impl DowngradeContext, +) -> Result<()> { + let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?; + let value = maybe_thunk(value.value().unwrap(), ctx)?; + attrs.insert(path, value, ctx) +} + +/// A stricter version of `downgrade_attrpathvalue` for `let...in` bindings. +/// It ensures that the attribute path contains no dynamic parts. +pub fn downgrade_static_attrpathvalue( + value: ast::AttrpathValue, + attrs: &mut AttrSet, + ctx: &mut impl DowngradeContext, +) -> Result<()> { + let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?; + if path.iter().any(|attr| matches!(attr, Attr::Dynamic(_))) { + return Err(Error::downgrade_error( + "dynamic attributes not allowed in let bindings".to_string(), + )); + } + let value = value.value().unwrap().downgrade(ctx)?; + attrs.insert(path, value, ctx) +} + +/// Helper function to downgrade entries with let bindings semantics. +/// This extracts common logic for both `rec` attribute sets and `let...in` expressions. +/// +/// Returns a tuple of (binding slots, body result) where: +/// - binding slots: pre-allocated expression slots for the bindings +/// - body result: the result of calling `body_fn` in the let scope +pub fn downgrade_let_bindings( + entries: Vec, + ctx: &mut Ctx, + body_fn: F, +) -> Result<(Vec, R)> +where + Ctx: DowngradeContext, + F: FnOnce(&mut Ctx, &[SymId]) -> Result, +{ + // 1. Collect all top-level binding keys + let mut binding_syms = HashSet::new(); + + for entry in &entries { + match entry { + ast::Entry::Inherit(inherit) => { + for attr in inherit.attrs() { + if let ast::Attr::Ident(ident) = attr { + binding_syms.insert(ctx.new_sym(ident.to_string())); + } + } + } + ast::Entry::AttrpathValue(value) => { + let attrpath = value.attrpath().unwrap(); + if let Some(first_attr) = attrpath.attrs().next() + && let ast::Attr::Ident(ident) = first_attr + { + binding_syms.insert(ctx.new_sym(ident.to_string())); + } + } + } + } + + let binding_keys: Vec<_> = binding_syms.into_iter().collect(); + + // 2. Reserve slots for bindings + let slots_iter = ctx.reserve_slots(binding_keys.len()); + let slots_clone = slots_iter.clone(); + + // 3. Create let scope bindings + let let_bindings: HashMap<_, _> = binding_keys.iter().copied().zip(slots_iter).collect(); + + // 4. Process entries in let scope + let body = ctx.with_let_scope(let_bindings, |ctx| { + // Collect all bindings in a temporary AttrSet + let mut temp_attrs = AttrSet { + stcs: HashMap::new(), + dyns: Vec::new(), + }; + + for entry in entries { + match entry { + ast::Entry::Inherit(inherit) => { + downgrade_inherit(inherit, &mut temp_attrs.stcs, ctx)?; + } + ast::Entry::AttrpathValue(value) => { + downgrade_static_attrpathvalue(value, &mut temp_attrs, ctx)?; + } + } + } + + // Fill pre-allocated slots with top-level bindings + for (sym, slot) in binding_keys.iter().copied().zip(slots_clone.clone()) { + if let Some(&expr) = temp_attrs.stcs.get(&sym) { + ctx.replace_expr(slot, Ir::Thunk(expr)); + } else { + return Err(Error::downgrade_error(format!( + "binding '{}' not found", + format_symbol(ctx.get_sym(sym)) + ))); + } + } + + // Call the body function with the binding keys + body_fn(ctx, &binding_keys) + })?; + + // 5. Return the slots and body + Ok((slots_clone.collect(), body)) +} diff --git a/nix-js/src/lib.rs b/nix-js/src/lib.rs new file mode 100644 index 0000000..f11b95c --- /dev/null +++ b/nix-js/src/lib.rs @@ -0,0 +1,9 @@ +pub mod codegen; +pub mod context; +pub mod error; +pub mod ir; +pub mod runtime; +pub mod value; + +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; diff --git a/nix-js/src/runtime.rs b/nix-js/src/runtime.rs new file mode 100644 index 0000000..56541ed --- /dev/null +++ b/nix-js/src/runtime.rs @@ -0,0 +1,149 @@ +use std::cell::RefCell; +use std::sync::Once; + +use crate::error::{Error, Result}; +use crate::value::{AttrSet, Const, List, Symbol, Value}; + +static INIT: Once = Once::new(); + +thread_local! { + static ISOLATE: RefCell = + RefCell::new(v8::Isolate::new(Default::default())); +} + +pub fn run(script: &str) -> Result { + INIT.call_once(|| { + v8::V8::initialize_platform(v8::new_default_platform(0, false).make_shared()); + v8::V8::initialize(); + }); + + ISOLATE.with_borrow_mut(|isolate| run_impl(script, isolate)) +} + +fn run_impl(script: &str, isolate: &mut v8::Isolate) -> Result { + let handle_scope = std::pin::pin!(v8::HandleScope::new(isolate)); + let handle_scope = &mut handle_scope.init(); + let context = v8::Context::new(handle_scope, v8::ContextOptions::default()); + let scope = &mut v8::ContextScope::new(handle_scope, context); + + let runtime_code = include_str!("./runtime/runtime.js"); + let runtime_source = v8::String::new(scope, runtime_code).unwrap(); + let runtime_script = v8::Script::compile(scope, runtime_source, None).unwrap(); + + if runtime_script.run(scope).is_none() { + return Err(Error::eval_error("Failed to initialize runtime".to_string())); + } + + let source = v8::String::new(scope, script).unwrap(); + + // Use TryCatch to capture JavaScript exceptions + let try_catch = std::pin::pin!(v8::TryCatch::new(scope)); + let try_catch = &mut try_catch.init(); + let script = match v8::Script::compile(try_catch, source, None) { + Some(script) => script, + None => { + if let Some(exception) = try_catch.exception() { + let exception_string = exception + .to_string(try_catch) + .unwrap() + .to_rust_string_lossy(try_catch); + return Err(Error::eval_error(format!("Compilation error: {}", exception_string))); + } else { + return Err(Error::eval_error("Unknown compilation error".to_string())); + } + } + }; + + match script.run(try_catch) { + Some(result) => Ok(to_value(result, try_catch)), + None => { + if let Some(exception) = try_catch.exception() { + let exception_string = exception + .to_string(try_catch) + .unwrap() + .to_rust_string_lossy(try_catch); + Err(Error::eval_error(format!("Runtime error: {}", exception_string))) + } else { + Err(Error::eval_error("Unknown runtime error".to_string())) + } + } + } +} + +fn to_value<'a>( + val: v8::Local<'a, v8::Value>, + scope: &v8::PinnedRef<'a, v8::HandleScope>, +) -> Value { + match () { + _ if val.is_int32() => { + let val = val.to_int32(scope).unwrap().value(); + Value::Const(Const::Int(val as i64)) + } + _ if val.is_big_int() => { + let (val, true) = val.to_big_int(scope).unwrap().i64_value() else { + todo!() + }; + Value::Const(Const::Int(val)) + } + _ if val.is_number() => { + let val = val.to_number(scope).unwrap().value(); + Value::Const(Const::Float(val)) + } + _ if val.is_true() => Value::Const(Const::Bool(true)), + _ if val.is_false() => Value::Const(Const::Bool(false)), + _ if val.is_null() => Value::Const(Const::Bool(true)), + _ if val.is_string() => { + let val = val.to_string(scope).unwrap(); + Value::String(val.to_rust_string_lossy(scope)) + } + _ if val.is_array() => { + let val = val.try_cast::().unwrap(); + let len = val.length(); + let list = (0..len) + .map(|i| { + let val = val.get_index(scope, i).unwrap(); + to_value(val, scope) + }) + .collect(); + Value::List(List::new(list)) + } + _ if val.is_object() => { + let val = val.to_object(scope).unwrap(); + let keys = val + .get_own_property_names(scope, v8::GetPropertyNamesArgsBuilder::new().build()) + .unwrap(); + let len = keys.length(); + let attrs = (0..len) + .map(|i| { + let key = keys.get_index(scope, i).unwrap(); + let val = val.get(scope, key).unwrap(); + let key = key.to_rust_string_lossy(scope); + (Symbol::new(key), to_value(val, scope)) + }) + .collect(); + Value::AttrSet(AttrSet::new(attrs)) + } + _ if val.is_function_template() => Value::PrimOp, + _ if val.is_function() => Value::Func, + _ => todo!("{}", val.type_repr()), + } +} + +#[test] +fn to_value_working() { + assert_eq!( + run("({ + test: [1, 9223372036854775807n, true, false, 'hello world!'] + })").unwrap(), + Value::AttrSet(AttrSet::new(std::collections::BTreeMap::from([( + Symbol::from("test"), + Value::List(List::new(vec![ + Value::Const(Const::Int(1)), + Value::Const(Const::Int(9223372036854775807)), + Value::Const(Const::Bool(true)), + Value::Const(Const::Bool(false)), + Value::String("hello world!".to_string()) + ])) + )]))) + ); +} diff --git a/nix-js/src/runtime/runtime.js b/nix-js/src/runtime/runtime.js new file mode 100644 index 0000000..ba17dcd --- /dev/null +++ b/nix-js/src/runtime/runtime.js @@ -0,0 +1,145 @@ +const NixRuntime = (() => { + const IS_THUNK = Symbol("is_thunk"); + + class NixThunk { + constructor(func) { + this[IS_THUNK] = true; + this.func = func; + this.is_forced = false; + this.result = null; + } + } + + const is_thunk = (value) => { + return value !== null && typeof value === "object" && value[IS_THUNK] === true; + }; + + const force = (value) => { + if (!is_thunk(value)) { + return value; + } + + if (value.is_forced) { + return value.result; + } + + const result = force(value.func()); + value.result = result; + value.is_forced = true; + + value.func = null; + + return result; + }; + + const create_thunk = (func) => new NixThunk(func); + + const create_lazy_set = (definitions) => { + const cache = new Map(); + return new Proxy({}, { + get: (_target, key) => { + if (cache.has(key)) { + return cache.get(key); + } + + if (key in definitions) { + const value = definitions[key](); + cache.set(key, value); + return value; + } + + return undefined; + } + }); + }; + + const trace = (msg, value) => { + console.log(`[TRACE] ${msg}`); + return force(value); + }; + + const select = (obj, key) => { + const forced_obj = force(obj); + const forced_key = force(key); + + if (forced_obj === null || forced_obj === undefined) { + throw new Error(`Cannot select '${forced_key}' from null/undefined`); + } + + if (!(forced_key in forced_obj)) { + throw new Error(`Attribute '${forced_key}' not found`); + } + + return forced_obj[forced_key]; + }; + + const select_with_default = (obj, key, default_val) => { + const forced_obj = force(obj); + const forced_key = force(key); + + if (forced_obj === null || forced_obj === undefined) { + return force(default_val); + } + + if (!(forced_key in forced_obj)) { + return force(default_val); + } + + return forced_obj[forced_key]; + }; + + const validate_params = (arg, required, allowed) => { + const forced_arg = force(arg); + + // Check required parameters + if (required) { + for (const key of required) { + if (!Object.prototype.hasOwnProperty.call(forced_arg, key)) { + throw new Error(`Function called without required argument '${key}'`); + } + } + } + + // Check allowed parameters (if not using ellipsis) + if (allowed) { + const allowed_set = new Set(allowed); + for (const key in forced_arg) { + if (!allowed_set.has(key)) { + throw new Error(`Function called with unexpected argument '${key}'`); + } + } + } + + return forced_arg; + }; + + const op = { + add: (a, b) => force(a) + force(b), + sub: (a, b) => force(a) - force(b), + mul: (a, b) => force(a) * force(b), + div: (a, b) => force(a) / force(b), + + eq: (a, b) => force(a) === force(b), + neq: (a, b) => force(a) !== force(b), + lt: (a, b) => force(a) < force(b), + lte: (a, b) => force(a) <= force(b), + gt: (a, b) => force(a) > force(b), + gte: (a, b) => force(a) >= force(b), + + band: (a, b) => force(a) && force(b), + bor: (a, b) => force(a) || force(b), + bnot: (a) => !force(a) + }; + + return { + create_thunk, + force, + is_thunk, + create_lazy_set, + trace, + select, + select_with_default, + validate_params, + op + }; +})(); diff --git a/nix-js/src/value.rs b/nix-js/src/value.rs new file mode 100644 index 0000000..31faf4a --- /dev/null +++ b/nix-js/src/value.rs @@ -0,0 +1,211 @@ +use core::fmt::{Debug, Display, Formatter, Result as FmtResult}; +use core::hash::Hash; +use core::ops::Deref; + +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::sync::LazyLock; + +use derive_more::{Constructor, IsVariant, Unwrap}; +use regex::Regex; + +/// Represents a constant, primitive value in Nix. +#[derive(Debug, Clone, Copy, PartialEq, IsVariant, Unwrap)] +pub enum Const { + /// A boolean value (`true` or `false`). + Bool(bool), + /// A 64-bit signed integer. + Int(i64), + /// A 64-bit floating-point number. + Float(f64), + /// The `null` value. + Null, +} + +impl Display for Const { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + use Const::*; + match self { + Int(x) => write!(f, "{x}"), + Float(x) => write!(f, "{x}"), + Bool(x) => write!(f, "{x}"), + Null => write!(f, "null"), + } + } +} + +impl From for Const { + fn from(value: bool) -> Self { + Const::Bool(value) + } +} + +impl From for Const { + fn from(value: i64) -> Self { + Const::Int(value) + } +} + +impl From for Const { + fn from(value: f64) -> Self { + Const::Float(value) + } +} + +/// Represents a Nix symbol, which is used as a key in attribute sets. +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)] +pub struct Symbol(String); + +impl> From for Symbol { + fn from(value: T) -> Self { + Symbol(value.into()) + } +} + +/// Formats a string slice as a Nix symbol, quoting it if necessary. +pub fn format_symbol<'a>(sym: impl Into>) -> Cow<'a, str> { + let sym = sym.into(); + if REGEX.is_match(&sym) { + sym + } else { + Cow::Owned(format!(r#""{sym}""#)) + } +} + +impl Display for Symbol { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + if self.normal() { + write!(f, "{}", self.0) + } else { + write!(f, r#""{}""#, self.0) + } + } +} + +static REGEX: LazyLock = + LazyLock::new(|| Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_'-]*$").unwrap()); +impl Symbol { + /// Checks if the symbol is a "normal" identifier that doesn't require quotes. + fn normal(&self) -> bool { + REGEX.is_match(self) + } +} + +impl Deref for Symbol { + type Target = str; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Symbol { + /// Consumes the `Symbol`, returning its inner `String`. + pub fn into_inner(self) -> String { + self.0 + } + + /// Returns a reference to the inner `String`. + pub fn as_inner(&self) -> &String { + &self.0 + } +} + +/// Represents a Nix attribute set, which is a map from symbols to values. +#[derive(Constructor, Clone, PartialEq)] +pub struct AttrSet { + data: BTreeMap, +} + +impl Debug for AttrSet { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + use Value::*; + write!(f, "{{")?; + for (k, v) in self.data.iter() { + write!(f, " {k:?} = ")?; + match v { + List(_) => write!(f, "[ ... ];")?, + AttrSet(_) => write!(f, "{{ ... }};")?, + v => write!(f, "{v:?};")?, + } + } + write!(f, " }}") + } +} + +impl Display for AttrSet { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + use Value::*; + write!(f, "{{ ")?; + let mut first = true; + for (k, v) in self.data.iter() { + if !first { + write!(f, "; ")?; + } + write!(f, "{k} = ")?; + match v { + AttrSet(_) => write!(f, "{{ ... }}"), + List(_) => write!(f, "[ ... ]"), + v => write!(f, "{v}"), + }?; + first = false; + } + write!(f, " }}") + } +} + +/// Represents a Nix list, which is a vector of values. +#[derive(Constructor, Clone, Debug, PartialEq)] +pub struct List { + data: Vec, +} + +impl Display for List { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "[ ")?; + for v in self.data.iter() { + write!(f, "{v} ")?; + } + write!(f, "]") + } +} + +/// Represents any possible Nix value that can be returned from an evaluation. +#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)] +pub enum Value { + /// A constant value (int, float, bool, null). + Const(Const), + /// A string value. + String(String), + /// An attribute set. + AttrSet(AttrSet), + /// A list. + List(List), + /// A thunk, representing a delayed computation. + Thunk, + /// A function (lambda). + Func, + /// A primitive (built-in) operation. + PrimOp, + /// A partially applied primitive operation. + PrimOpApp, + /// A marker for a value that has been seen before during serialization, to break cycles. + /// This is used to prevent infinite recursion when printing or serializing cyclic data structures. + Repeated, +} + +impl Display for Value { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + use Value::*; + match self { + Const(x) => write!(f, "{x}"), + String(x) => write!(f, r#""{x}""#), + AttrSet(x) => write!(f, "{x}"), + List(x) => write!(f, "{x}"), + Thunk => write!(f, ""), + Func => write!(f, ""), + PrimOp => write!(f, ""), + PrimOpApp => write!(f, ""), + Repeated => write!(f, ""), + } + } +}