From 1a7230ce9be6ed4361bf07f87916c42ebcf6b2f2 Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Mon, 12 Jan 2026 01:38:10 +0100 Subject: [PATCH] Traditional route appears to work Beginnings of dylib extensions, entirely untestted --- Cargo.lock | 770 +++++++++++----------------- async-fn-stream/Cargo.toml | 2 +- orchid-api-derive/Cargo.toml | 6 +- orchid-api-traits/Cargo.toml | 2 +- orchid-api/Cargo.toml | 4 +- orchid-api/src/binary.rs | 75 ++- orchid-api/src/logging.rs | 24 +- orchid-api/src/proto.rs | 19 +- orchid-base/Cargo.toml | 10 +- orchid-base/src/binary.rs | 118 +++++ orchid-base/src/error.rs | 1 + orchid-base/src/lib.rs | 1 + orchid-base/src/logging.rs | 93 ++-- orchid-base/src/reqnot.rs | 182 +++++-- orchid-extension/Cargo.toml | 16 +- orchid-extension/src/atom_owned.rs | 3 +- orchid-extension/src/atom_thin.rs | 4 +- orchid-extension/src/binary.rs | 30 ++ orchid-extension/src/entrypoint.rs | 61 ++- orchid-extension/src/interner.rs | 6 +- orchid-extension/src/lib.rs | 2 + orchid-extension/src/logger.rs | 57 ++ orchid-extension/src/system_ctor.rs | 3 +- orchid-extension/src/tokio.rs | 11 +- orchid-host/Cargo.toml | 16 +- orchid-host/src/ctx.rs | 8 +- orchid-host/src/dylib.rs | 55 ++ orchid-host/src/execute.rs | 4 +- orchid-host/src/extension.rs | 381 +++++++------- orchid-host/src/lib.rs | 2 + orchid-host/src/logger.rs | 84 +++ orchid-host/src/subprocess.rs | 36 +- orchid-host/src/system.rs | 6 +- orchid-std/Cargo.toml | 20 +- orchid-std/src/lib.rs | 9 + orchid-std/src/macros/resolve.rs | 14 +- orcx/Cargo.toml | 12 +- orcx/src/main.rs | 544 +++++++++++--------- unsync-pipe/Cargo.toml | 2 +- xtask/Cargo.toml | 2 +- 40 files changed, 1560 insertions(+), 1135 deletions(-) create mode 100644 orchid-base/src/binary.rs create mode 100644 orchid-extension/src/binary.rs create mode 100644 orchid-extension/src/logger.rs create mode 100644 orchid-host/src/dylib.rs create mode 100644 orchid-host/src/logger.rs diff --git a/Cargo.lock b/Cargo.lock index d4dfa6e..133e3ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "ahash" version = "0.7.8" @@ -98,54 +83,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - [[package]] name = "async-fn-stream" version = "0.1.0" dependencies = [ "futures", - "test_executors 0.3.5", -] - -[[package]] -name = "async-io" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" -dependencies = [ - "async-lock", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix 0.38.43", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", + "test_executors", ] [[package]] @@ -155,47 +98,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" [[package]] -name = "async-process" -version = "2.4.0" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix 1.0.8", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", ] -[[package]] -name = "async-signal" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 0.38.43", - "signal-hook-registry", - "slab", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "atomic-waker" version = "1.1.2" @@ -208,21 +120,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - [[package]] name = "bitflags" version = "2.6.0" @@ -251,25 +148,12 @@ dependencies = [ ] [[package]] -name = "blocking" -version = "1.6.1" +name = "block2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "blocking_semaphore" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88376f98b48312155a0ba2f868ad705f5d5b9a1065514b1f827e31c1d2f3dbb0" -dependencies = [ - "logwise 0.4.0", + "objc2", ] [[package]] @@ -290,9 +174,9 @@ checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate", - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", ] [[package]] @@ -324,8 +208,8 @@ version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2 1.0.104", + "quote 1.0.42", "syn 1.0.109", ] @@ -343,9 +227,25 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "camino" -version = "1.1.9" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +dependencies = [ + "find-msvc-tools", + "shlex", +] [[package]] name = "cfg-if" @@ -361,9 +261,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.24" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9560b07a799281c7e0958b9296854d6fafd4c5f31444a7e5bb1ad6dde5ccf1bd" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -371,9 +271,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.24" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874e0dd3eb68bf99058751ac9712f622e61e6f393a94f7128fa26e3f02f5c7cd" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -383,14 +283,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", ] [[package]] @@ -405,15 +305,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "const_panic" version = "0.2.14" @@ -444,12 +335,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - [[package]] name = "crypto-common" version = "0.1.6" @@ -462,12 +347,13 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.5" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" +checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" dependencies = [ + "dispatch2", "nix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -491,6 +377,18 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", +] + [[package]] name = "dyn-clone" version = "1.0.20" @@ -510,41 +408,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "errno" -version = "0.3.10" +name = "find-msvc-tools" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "foldhash" @@ -606,19 +473,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" -[[package]] -name = "futures-lite" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-locks" version = "0.7.1" @@ -636,9 +490,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", ] [[package]] @@ -704,12 +558,6 @@ dependencies = [ "wasip2", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "hashbrown" version = "0.12.3" @@ -721,15 +569,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", @@ -742,12 +584,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - [[package]] name = "include_dir" version = "0.7.4" @@ -763,29 +599,18 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2 1.0.104", + "quote 1.0.42", ] [[package]] name = "indexmap" -version = "2.7.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.15.2", -] - -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags", - "cfg-if", - "libc", + "hashbrown 0.16.1", ] [[package]] @@ -821,9 +646,9 @@ dependencies = [ [[package]] name = "konst" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64896bdfd7906cfb0b57bc04f08bde408bcd6aaf71ff438ee471061cd16f2e86" +checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d" dependencies = [ "const_panic", "konst_proc_macros", @@ -832,9 +657,9 @@ dependencies = [ [[package]] name = "konst_proc_macros" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf92d396aa2df203577ebef8deaf1efc24d446366ca86be83ec8ac794b157d6" +checksum = "e037a2e1d8d5fdbd49b16a4ea09d5d6401c1f29eca5ff29d03d3824dba16256a" [[package]] name = "lazy_static" @@ -849,16 +674,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "libloading" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] [[package]] -name = "linux-raw-sys" -version = "0.9.4" +name = "libm" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "lock_api" @@ -884,24 +713,11 @@ dependencies = [ [[package]] name = "logwise" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09231cef85ff27c65aeadf05f249fb80d6b38c904ac9eb861c1932d4b6c3308" +checksum = "2e61b6fe8ee4df192fa5c94311a8ded65a083afcb9c0740f2419be135f9fb378" dependencies = [ - "logwise_proc 0.3.0", - "wasm-bindgen", - "wasm_thread", - "web-sys", - "web-time", -] - -[[package]] -name = "logwise" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d72d41b7fd35d1314749844b571794e635721b3a78b30d275b5f430dedf0723e" -dependencies = [ - "logwise_proc 0.4.0", + "logwise_proc 0.5.0", "wasm-bindgen", "wasm_safe_mutex", "wasm_thread", @@ -917,15 +733,9 @@ checksum = "7ba61263a12347d87ece9286fab931b7f3407b06a94ab952e676b350d7054a57" [[package]] name = "logwise_proc" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7494645c8b485fcc0acaa8f382ef8cc081110838536a380e3b3048427f628306" - -[[package]] -name = "logwise_proc" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc76389d352c1151645790c3cf157a8b2b84b5faf081fd25686a146e253fb0a8" +checksum = "1c980f57d231d31d90700b8a9440f42f237f09d8ad02e82636140e3d760b2768" [[package]] name = "memchr" @@ -940,12 +750,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" [[package]] -name = "miniz_oxide" -version = "0.8.3" +name = "minicov" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" dependencies = [ - "adler2", + "cc", + "walkdir", ] [[package]] @@ -967,9 +778,9 @@ checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", @@ -977,6 +788,15 @@ dependencies = [ "libc", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -984,23 +804,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] -name = "object" -version = "0.36.7" +name = "objc2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" dependencies = [ - "memchr", + "objc2-encode", ] +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "orchid-api" version = "0.1.0" @@ -1010,7 +843,7 @@ dependencies = [ "orchid-api-derive", "orchid-api-traits", "ordered-float", - "test_executors 0.3.5", + "test_executors", "unsync-pipe", ] @@ -1020,9 +853,9 @@ version = "0.1.0" dependencies = [ "itertools", "orchid-api-traits", - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", ] [[package]] @@ -1045,7 +878,7 @@ dependencies = [ "derive_destructure", "dyn-clone", "futures", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "itertools", "lazy_static", "never", @@ -1058,7 +891,7 @@ dependencies = [ "rust-embed", "substack", "task-local", - "test_executors 0.4.0", + "test_executors", "trait-set", "unsync-pipe", ] @@ -1074,7 +907,7 @@ dependencies = [ "dyn-clone", "futures", "futures-locks", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "include_dir", "itertools", "konst", @@ -1101,14 +934,14 @@ version = "0.1.0" dependencies = [ "async-fn-stream", "async-once-cell", - "async-process", "bound", "derive_destructure", "futures", "futures-locks", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "itertools", "lazy_static", + "libloading", "memo-map", "never", "num-traits", @@ -1118,8 +951,11 @@ dependencies = [ "ordered-float", "pastey", "substack", - "test_executors 0.3.5", + "test_executors", + "tokio", + "tokio-util", "trait-set", + "unsync-pipe", ] [[package]] @@ -1129,7 +965,7 @@ dependencies = [ "async-fn-stream", "async-once-cell", "futures", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "itertools", "never", "once_cell", @@ -1143,7 +979,7 @@ dependencies = [ "rust_decimal", "subslice-offset", "substack", - "test_executors 0.3.5", + "test_executors", "tokio", ] @@ -1166,19 +1002,13 @@ dependencies = [ [[package]] name = "ordered-float" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" dependencies = [ "num-traits", ] -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - [[package]] name = "parking_lot" version = "0.12.3" @@ -1204,9 +1034,9 @@ dependencies = [ [[package]] name = "pastey" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" [[package]] name = "pin-project-lite" @@ -1220,32 +1050,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "polling" -version = "3.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 0.38.43", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1263,9 +1067,9 @@ checksum = "3331288c73a29bd726cd92d947059399bf8e4b01f13d24f02caecd5187e5b5d5" [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] @@ -1281,9 +1085,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] @@ -1303,8 +1107,8 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2 1.0.104", + "quote 1.0.42", "syn 1.0.109", ] @@ -1319,11 +1123,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ - "proc-macro2 1.0.101", + "proc-macro2 1.0.104", ] [[package]] @@ -1408,9 +1212,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1420,9 +1224,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1468,16 +1272,16 @@ version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2 1.0.104", + "quote 1.0.42", "syn 1.0.109", ] [[package]] name = "rust-embed" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -1486,22 +1290,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2 1.0.104", + "quote 1.0.42", "rust-embed-utils", - "syn 2.0.106", + "syn 2.0.112", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.7.2" +version = "8.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" dependencies = [ "sha2", "walkdir", @@ -1509,9 +1313,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.38.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8975fc98059f365204d635119cf9c5a60ae67b841ed49b5422a9a7e56cdfac0" +checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" dependencies = [ "arrayvec", "borsh", @@ -1523,38 +1327,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustix" -version = "0.38.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" -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" @@ -1590,22 +1362,32 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +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.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", ] [[package]] @@ -1631,6 +1413,12 @@ dependencies = [ "digest", ] +[[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.2" @@ -1673,9 +1461,9 @@ dependencies = [ [[package]] name = "some_executor" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb94ffe205bde5bf9db6972e4ba7e316ec79568df830627a2388abb5d2c2907" +checksum = "20a08b35ab786d4a98e1851053dcdf13afe10a4ec5db318dd29f43e8b88768f7" dependencies = [ "atomic-waker", "continue", @@ -1727,19 +1515,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2 1.0.104", + "quote 1.0.42", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.106" +version = "2.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2 1.0.104", + "quote 1.0.42", "unicode-ident", ] @@ -1760,26 +1548,11 @@ dependencies = [ [[package]] name = "test_executors" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b357ed2d52813470402c94b6a27db6cab2d06c42f28eba9eb4fa6c2491e43d" +checksum = "7deda896160c1fdd0f37a3c271e048313eb641373147aaa7758fe58654263adc" dependencies = [ - "blocking_semaphore", - "logwise 0.3.0", - "some_executor", - "test_executors_proc", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-time", -] - -[[package]] -name = "test_executors" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18edea7d204c604e14fde7c82ce4685b60a8e8bef019ea7b8dc053e7995f7ffc" -dependencies = [ - "logwise 0.4.0", + "logwise 0.5.0", "some_executor", "test_executors_proc", "wasm-bindgen", @@ -1789,12 +1562,14 @@ dependencies = [ [[package]] name = "test_executors_proc" -version = "0.3.0" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b59087b1e1332227b88c6e0ac2ca8fefccb3e2ae2caa48690a5f9cca851a2e7e" +checksum = "b68fbf53af4a8dfdf0bd9192ab2f6fd42e5b59454bcde58fe1382a8677087732" dependencies = [ - "quote 1.0.40", - "syn 2.0.106", + "proc-macro-crate", + "quote 1.0.42", + "syn 2.0.112", + "wasm-bindgen-test", ] [[package]] @@ -1812,9 +1587,9 @@ version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", ] [[package]] @@ -1834,40 +1609,37 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", ] [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -1879,45 +1651,42 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", + "toml_parser", "winnow", ] [[package]] -name = "tracing" -version = "0.1.41" +name = "toml_parser" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ - "pin-project-lite", - "tracing-core", + "winnow", ] -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" - [[package]] name = "trait-set" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", + "proc-macro2 1.0.104", + "quote 1.0.42", "syn 1.0.109", ] @@ -1954,7 +1723,7 @@ dependencies = [ "itertools", "rand 0.9.2", "rand_chacha 0.9.0", - "test_executors 0.4.0", + "test_executors", ] [[package]] @@ -2032,7 +1801,7 @@ version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ - "quote 1.0.40", + "quote 1.0.42", "wasm-bindgen-macro-support", ] @@ -2043,9 +1812,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", "wasm-bindgen-shared", ] @@ -2058,6 +1827,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-bindgen-test" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e90e66d265d3a1efc0e72a54809ab90b9c0c515915c67cdf658689d2c22c6c" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1" +dependencies = [ + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", +] + [[package]] name = "wasm_safe_mutex" version = "0.1.2" @@ -2111,6 +1912,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[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" @@ -2129,6 +1936,15 @@ dependencies = [ "windows-targets", ] +[[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" @@ -2195,9 +2011,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.22" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -2240,7 +2056,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2 1.0.101", - "quote 1.0.40", - "syn 2.0.106", + "proc-macro2 1.0.104", + "quote 1.0.42", + "syn 2.0.112", ] diff --git a/async-fn-stream/Cargo.toml b/async-fn-stream/Cargo.toml index 424e45a..c7ac8cf 100644 --- a/async-fn-stream/Cargo.toml +++ b/async-fn-stream/Cargo.toml @@ -7,4 +7,4 @@ edition = "2024" futures = { version = "0.3.31", features = ["std"], default-features = false } [dev-dependencies] -test_executors = "0.3.5" +test_executors = "0.4.1" diff --git a/orchid-api-derive/Cargo.toml b/orchid-api-derive/Cargo.toml index 2b513fc..50c80b8 100644 --- a/orchid-api-derive/Cargo.toml +++ b/orchid-api-derive/Cargo.toml @@ -9,8 +9,8 @@ proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -quote = "1.0.40" -syn = { version = "2.0.106" } +quote = "1.0.42" +syn = { version = "2.0.112" } orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" } -proc-macro2 = "1.0.101" +proc-macro2 = "1.0.104" itertools = "0.14.0" diff --git a/orchid-api-traits/Cargo.toml b/orchid-api-traits/Cargo.toml index 2f6622b..6f7bdd5 100644 --- a/orchid-api-traits/Cargo.toml +++ b/orchid-api-traits/Cargo.toml @@ -9,4 +9,4 @@ edition = "2024" futures = { version = "0.3.31", features = ["std"], default-features = false } itertools = "0.14.0" never = "0.1.0" -ordered-float = "5.0.0" +ordered-float = "5.1.0" diff --git a/orchid-api/Cargo.toml b/orchid-api/Cargo.toml index d2492f2..843e38a 100644 --- a/orchid-api/Cargo.toml +++ b/orchid-api/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ordered-float = "5.0.0" +ordered-float = "5.1.0" orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" } orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" } futures = { version = "0.3.31", features = ["std"], default-features = false } @@ -14,4 +14,4 @@ itertools = "0.14.0" unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" } [dev-dependencies] -test_executors = "0.3.5" +test_executors = "0.4.1" diff --git a/orchid-api/src/binary.rs b/orchid-api/src/binary.rs index b58eb1f..6b3ff8a 100644 --- a/orchid-api/src/binary.rs +++ b/orchid-api/src/binary.rs @@ -1,32 +1,46 @@ //! # Binary extension definition //! //! A binary extension is a DLL / shared object / dylib with a symbol called -//! `orchid_extension_main` which accepts a single argument of type [ExtCtx]. -//! Once that is received, communication continuees through the channel with the -//! same protocol outlined in [crate::proto] +//! `orchid_extension_main` which accepts a single argument of type +//! [ExtensionContext]. Once that is received, communication continuees through +//! the channel with the same protocol outlined in [crate::proto] use unsync_pipe::{Reader, Writer}; /// !Send !Sync owned waker +/// +/// This object is [Clone] for convenience but it has `drop` and no `clone` so +/// interactions must reflect a single logical owner +#[derive(Clone, Copy)] #[repr(C)] pub struct OwnedWakerVT { - data: *const (), + pub data: *const (), /// `self` - drop: extern "C" fn(*const ()), + pub drop: extern "C" fn(*const ()), + /// `self` + pub wake: extern "C" fn(*const ()), /// `&self` - wake: extern "C" fn(*const ()), + pub wake_ref: extern "C" fn(*const ()), } /// !Send !Sync, equivalent to `&mut Context<'a>`, hence no `drop`. /// When received in [FutureVT::poll], it must not outlive the call. +/// +/// You cannot directly wake using this waker, because such a trampoline would +/// pass through the binary interface twice for no reason. An efficient +/// implementation should implement that trampoline action internally, whereas +/// an inefficient but compliant implementation can clone a fresh waker and use +/// it up. +#[derive(Clone, Copy)] #[repr(C)] pub struct FutureContextVT { - data: *const (), + pub data: *const (), /// `&self` - waker: extern "C" fn(*const ()) -> OwnedWakerVT, + pub waker: extern "C" fn(*const ()) -> OwnedWakerVT, } /// ABI-stable `Poll<()>` +#[derive(Clone, Copy)] #[repr(C)] pub enum UnitPoll { Pending, @@ -34,34 +48,43 @@ pub enum UnitPoll { } /// ABI-stable `Pin>>` +/// +/// This object is [Clone] for convenience, but it has `drop` and no `clone` so +/// interactions must reflect a single logical owner +#[derive(Clone, Copy)] #[repr(C)] pub struct FutureVT { - data: *const (), + pub data: *const (), /// `self` - drop: extern "C" fn(*const ()), + pub drop: extern "C" fn(*const ()), /// `&mut self` Equivalent to [Future::poll] - poll: extern "C" fn(*const (), FutureContextVT) -> UnitPoll, + pub poll: extern "C" fn(*const (), FutureContextVT) -> UnitPoll, } -/// Owned extension context. +/// Handle for a runtime that allows its holder to spawn futures across dynamic +/// library boundaries +#[derive(Clone, Copy)] +#[repr(C)] +pub struct Spawner { + pub data: *const (), + /// `self` + pub drop: extern "C" fn(*const ()), + /// `&self` Add a future to this extension's task + pub spawn: extern "C" fn(*const (), FutureVT), +} + +/// Extension context. /// -/// When an extension starts, this is passed to +/// This struct is a plain old value, all of the contained values have a +/// distinct `drop` member #[repr(C)] pub struct ExtensionContext { - data: *const (), - /// `self` - drop: extern "C" fn(*const ()), - /// `self` upgrade to a later version of this struct. May only be called if - /// none of the other elements have been used yet. If a newer version isn't - /// supported, the server must return null, otherwise the the return value is - /// a pointer to the immediate next version of this struct - next: extern "C" fn(*const ()) -> *const (), - /// `&self` Add a future to this extension's task - spawn: extern "C" fn(*const (), FutureVT), + /// Spawns tasks associated with this extension + pub spawner: Spawner, /// serialized [crate::HostExtChannel] - input: Reader, + pub input: Reader, /// serialized [crate::ExtHostChannel] - output: Writer, + pub output: Writer, /// UTF-8 log stream directly to log service. - log: Writer, + pub log: Writer, } diff --git a/orchid-api/src/logging.rs b/orchid-api/src/logging.rs index 7324034..1386aab 100644 --- a/orchid-api/src/logging.rs +++ b/orchid-api/src/logging.rs @@ -1,14 +1,30 @@ +use std::collections::HashMap; + use orchid_api_derive::{Coding, Hierarchy}; -use crate::ExtHostNotif; +use crate::{ExtHostNotif, TStr}; +/// Describes what to do with a log stream. +/// Log streams are unstructured utf8 text unless otherwise stated. #[derive(Clone, Debug, Coding, PartialEq, Eq, Hash)] pub enum LogStrategy { - StdErr, - File(String), + /// Context-dependent default stream, often stderr + Default, + /// A file on the local filesystem + File { path: String, append: bool }, + /// Discard any log output Discard, } +#[derive(Clone, Debug, Coding)] +pub struct Logger { + pub routing: HashMap, + pub default: Option, +} + #[derive(Clone, Debug, Coding, Hierarchy)] #[extends(ExtHostNotif)] -pub struct Log(pub String); +pub struct Log { + pub category: TStr, + pub message: String, +} diff --git a/orchid-api/src/proto.rs b/orchid-api/src/proto.rs index 95f2d4e..19c35f4 100644 --- a/orchid-api/src/proto.rs +++ b/orchid-api/src/proto.rs @@ -34,23 +34,18 @@ use crate::{Sweeped, atom, expr, interner, lexer, logging, parser, system, tree} static HOST_INTRO: &[u8] = b"Orchid host, binary API v0\n"; #[derive(Clone, Debug)] pub struct HostHeader { - pub log_strategy: logging::LogStrategy, - pub msg_logs: logging::LogStrategy, + pub logger: logging::Logger, } impl Decode for HostHeader { async fn decode(mut read: Pin<&mut R>) -> io::Result { read_exact(read.as_mut(), HOST_INTRO).await?; - Ok(Self { - log_strategy: logging::LogStrategy::decode(read.as_mut()).await?, - msg_logs: logging::LogStrategy::decode(read.as_mut()).await?, - }) + Ok(Self { logger: logging::Logger::decode(read).await? }) } } impl Encode for HostHeader { async fn encode(&self, mut write: Pin<&mut W>) -> io::Result<()> { write.write_all(HOST_INTRO).await?; - self.log_strategy.encode(write.as_mut()).await?; - self.msg_logs.encode(write.as_mut()).await + self.logger.encode(write.as_mut()).await } } @@ -159,19 +154,19 @@ impl MsgSet for HostMsgSet { #[cfg(test)] mod tests { + use std::collections::HashMap; + use orchid_api_traits::enc_vec; use ordered_float::NotNan; use test_executors::spin_on; use super::*; + use crate::Logger; #[test] fn host_header_enc() { spin_on(async { - let hh = HostHeader { - log_strategy: logging::LogStrategy::File("SomeFile".to_string()), - msg_logs: logging::LogStrategy::File("SomeFile".to_string()), - }; + let hh = HostHeader { logger: Logger { routing: HashMap::new(), default: None } }; let mut enc = &enc_vec(&hh)[..]; eprintln!("Encoded to {enc:?}"); HostHeader::decode(Pin::new(&mut enc)).await.unwrap(); diff --git a/orchid-base/Cargo.toml b/orchid-base/Cargo.toml index 106406b..f777d6b 100644 --- a/orchid-base/Cargo.toml +++ b/orchid-base/Cargo.toml @@ -13,7 +13,7 @@ bound = "0.6.0" derive_destructure = "1.0.0" dyn-clone = "1.0.20" futures = { version = "0.3.31", features = ["std"], default-features = false } -hashbrown = "0.16.0" +hashbrown = "0.16.1" itertools = "0.14.0" lazy_static = "1.5.0" never = "0.1.0" @@ -21,13 +21,13 @@ num-traits = "0.2.19" orchid-api = { version = "0.1.0", path = "../orchid-api" } orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" } orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" } -ordered-float = "5.0.0" -regex = "1.11.2" -rust-embed = "8.7.2" +ordered-float = "5.1.0" +regex = "1.12.2" +rust-embed = "8.9.0" substack = "1.1.1" trait-set = "0.3.0" task-local = "0.1.0" [dev-dependencies] futures = "0.3.31" -test_executors = "0.4.0" +test_executors = "0.4.1" diff --git a/orchid-base/src/binary.rs b/orchid-base/src/binary.rs new file mode 100644 index 0000000..7b57df5 --- /dev/null +++ b/orchid-base/src/binary.rs @@ -0,0 +1,118 @@ +use std::pin::Pin; +use std::rc::Rc; +use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + +use orchid_api::binary::{FutureContextVT, FutureVT, OwnedWakerVT, UnitPoll}; + +type WideBox = Box>; + +static OWNED_VTABLE: RawWakerVTable = RawWakerVTable::new( + |data| { + let data = unsafe { Rc::::from_raw(data as *const _) }; + let val = RawWaker::new(Rc::into_raw(data.clone()) as *const (), &OWNED_VTABLE); + // Clone must create a duplicate of the Rc, so it has to be un-leaked, cloned, + // then leaked again. + let _ = Rc::into_raw(data); + val + }, + |data| { + // Wake must awaken the task and then clean up the state, so the waker must be + // un-leaked + let data = unsafe { Rc::::from_raw(data as *const _) }; + (data.wake)(data.data); + }, + |data| { + // Wake-by-ref must awaken the task while preserving the future, so the Rc is + // untouched + let data = unsafe { (data as *const OwnedWakerVT).as_ref() }.unwrap(); + (data.wake_ref)(data.data); + }, + |data| { + // Drop must clean up the state, so the waker must be un-leaked + let data = unsafe { Rc::::from_raw(data as *const _) }; + (data.drop)(data.data); + }, +); + +struct BorrowedWakerData<'a> { + go_around: &'a mut bool, + cx: FutureContextVT, +} +static BORROWED_VTABLE: RawWakerVTable = RawWakerVTable::new( + |data| { + let data = unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap(); + let owned_data = Rc::::new((data.cx.waker)(data.cx.data)); + RawWaker::new(Rc::into_raw(owned_data) as *const (), &OWNED_VTABLE) + }, + |data| *unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap().go_around = true, + |data| *unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap().go_around = true, + |_data| {}, +); + +/// Convert a future to a binary-compatible format that can be sent across +/// dynamic library boundaries +pub fn future_to_vt + 'static>(fut: Fut) -> FutureVT { + let wide_box = Box::new(fut) as WideBox; + let data = Box::into_raw(Box::new(wide_box)); + extern "C" fn drop(raw: *const ()) { + std::mem::drop(unsafe { Box::::from_raw(raw as *mut _) }) + } + extern "C" fn poll(raw: *const (), cx: FutureContextVT) -> UnitPoll { + let mut this = unsafe { Pin::new_unchecked(&mut **(raw as *mut WideBox).as_mut().unwrap()) }; + loop { + let mut go_around = false; + let borrowed_waker = unsafe { + Waker::from_raw(RawWaker::new( + &mut BorrowedWakerData { go_around: &mut go_around, cx } as *mut _ as *const (), + &BORROWED_VTABLE, + )) + }; + let mut ctx = Context::from_waker(&borrowed_waker); + let result = this.as_mut().poll(&mut ctx); + if matches!(result, Poll::Ready(())) { + break UnitPoll::Ready; + } + if !go_around { + break UnitPoll::Pending; + } + } + } + FutureVT { data: data as *const _, drop, poll } +} + +struct VirtualFuture { + vt: FutureVT, +} +impl Unpin for VirtualFuture {} +impl Future for VirtualFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + extern "C" fn waker(raw: *const ()) -> OwnedWakerVT { + let waker = unsafe { (raw as *mut Context).as_mut() }.unwrap().waker().clone(); + let data = Box::into_raw(Box::::new(waker)) as *const (); + return OwnedWakerVT { data, drop, wake, wake_ref }; + extern "C" fn drop(raw: *const ()) { + std::mem::drop(unsafe { Box::::from_raw(raw as *mut Waker) }) + } + extern "C" fn wake(raw: *const ()) { + unsafe { Box::::from_raw(raw as *mut Waker) }.wake(); + } + extern "C" fn wake_ref(raw: *const ()) { + unsafe { (raw as *mut Waker).as_mut() }.unwrap().wake_by_ref(); + } + } + let cx = FutureContextVT { data: cx as *mut Context as *const (), waker }; + let result = (self.vt.poll)(self.vt.data, cx); + match result { + UnitPoll::Pending => Poll::Pending, + UnitPoll::Ready => Poll::Ready(()), + } + } +} +impl Drop for VirtualFuture { + fn drop(&mut self) { (self.vt.drop)(self.vt.data) } +} + +/// Receive a future sent across dynamic library boundaries and convert it into +/// an owned object +pub fn vt_to_future(vt: FutureVT) -> impl Future { VirtualFuture { vt } } diff --git a/orchid-base/src/error.rs b/orchid-base/src/error.rs index c097bc4..fd7f577 100644 --- a/orchid-base/src/error.rs +++ b/orchid-base/src/error.rs @@ -128,6 +128,7 @@ impl OrcErrv { pub async fn from_api<'a>(api: impl IntoIterator) -> Self { Self(join_all(api.into_iter().map(OrcErr::from_api)).await) } + pub fn iter(&self) -> impl Iterator + '_ { self.0.iter().cloned() } } impl From for OrcErrv { fn from(value: OrcErr) -> Self { Self(vec![value]) } diff --git a/orchid-base/src/lib.rs b/orchid-base/src/lib.rs index 654811d..ec6c970 100644 --- a/orchid-base/src/lib.rs +++ b/orchid-base/src/lib.rs @@ -1,6 +1,7 @@ pub use async_once_cell; use orchid_api as api; +pub mod binary; pub mod box_cow; pub mod boxed_iter; pub mod char_filter; diff --git a/orchid-base/src/logging.rs b/orchid-base/src/logging.rs index 0f35f62..f1ba853 100644 --- a/orchid-base/src/logging.rs +++ b/orchid-base/src/logging.rs @@ -1,47 +1,74 @@ +use std::any::Any; +use std::cell::RefCell; use std::fmt::Arguments; -use std::fs::File; -use std::io::{Write, stderr}; +use std::io::Write; +use std::rc::Rc; -pub use api::LogStrategy; -use itertools::Itertools; +use futures::future::LocalBoxFuture; use task_local::task_local; use crate::api; -#[derive(Clone)] -pub struct Logger(api::LogStrategy); -impl Logger { - pub fn new(strat: api::LogStrategy) -> Self { Self(strat) } - pub fn log(&self, msg: impl AsRef) { writeln!(self, "{}", msg.as_ref()) } - pub fn strat(&self) -> api::LogStrategy { self.0.clone() } - pub fn is_active(&self) -> bool { !matches!(self.0, api::LogStrategy::Discard) } - pub fn log_buf(&self, event: impl AsRef, buf: &[u8]) { - if std::env::var("ORCHID_LOG_BUFFERS").is_ok_and(|v| !v.is_empty()) { - writeln!(self, "{}: [{}]", event.as_ref(), buf.iter().map(|b| format!("{b:02x}")).join(" ")) - } - } - pub fn write_fmt(&self, fmt: Arguments) { - match &self.0 { - api::LogStrategy::Discard => (), - api::LogStrategy::StdErr => { - stderr().write_fmt(fmt).expect("Could not write to stderr!"); - stderr().flush().expect("Could not flush stderr") - }, - api::LogStrategy::File(f) => { - let mut file = (File::options().write(true).create(true).truncate(true).open(f)) - .expect("Could not open logfile"); - file.write_fmt(fmt).expect("Could not write to logfile"); - }, - } +task_local! { + static DEFAULT_WRITER: RefCell> +} + +/// Set the stream used for [api::LogStrategy::Default]. If not set, +/// [std::io::stderr] will be used. +pub async fn with_default_stream(stderr: impl Write + 'static, fut: F) -> F::Output { + DEFAULT_WRITER.scope(RefCell::new(Box::new(stderr)), fut).await +} + +pub trait LogWriter { + fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()>; +} + +pub trait Logger: Any { + fn writer(&self, category: &str) -> Rc; + fn strat(&self, category: &str) -> api::LogStrategy; + fn is_active(&self, category: &str) -> bool { + !matches!(self.strat(category), api::LogStrategy::Discard) } } task_local! { - static LOGGER: Logger; + static LOGGER: Rc; } -pub async fn with_logger(logger: Logger, fut: F) -> F::Output { - LOGGER.scope(logger, fut).await +pub async fn with_logger(logger: impl Logger + 'static, fut: F) -> F::Output { + LOGGER.scope(Rc::new(logger), fut).await } -pub fn logger() -> Logger { LOGGER.try_with(|l| l.clone()).expect("Logger not set!") } +pub fn log(category: &str) -> Rc { + LOGGER.try_with(|l| l.writer(category)).expect("Logger not set!") +} + +pub fn get_logger() -> Rc { LOGGER.try_with(|l| l.clone()).expect("Logger not set!") } + +pub mod test { + use std::fmt::Arguments; + use std::rc::Rc; + + use futures::future::LocalBoxFuture; + + use crate::clone; + use crate::logging::{LogWriter, Logger}; + + #[derive(Clone)] + pub struct TestLogger(Rc LocalBoxFuture<'static, ()>>); + impl LogWriter for TestLogger { + fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> { + (self.0)(fmt.to_string()) + } + } + impl Logger for TestLogger { + fn strat(&self, _category: &str) -> orchid_api::LogStrategy { orchid_api::LogStrategy::Default } + fn writer(&self, _category: &str) -> std::rc::Rc { Rc::new(self.clone()) } + } + impl TestLogger { + pub fn new(f: impl AsyncFn(String) + 'static) -> Self { + let f = Rc::new(f); + Self(Rc::new(move |s| clone!(f; Box::pin(async move { f(s).await })))) + } + } +} diff --git a/orchid-base/src/reqnot.rs b/orchid-base/src/reqnot.rs index 4af976b..53d9768 100644 --- a/orchid-base/src/reqnot.rs +++ b/orchid-base/src/reqnot.rs @@ -1,5 +1,4 @@ use std::cell::RefCell; -use std::future::Future; use std::marker::PhantomData; use std::pin::{Pin, pin}; use std::rc::Rc; @@ -316,13 +315,25 @@ impl CommCtx { pub fn io_comm( o: Rc>>>, i: Mutex>>, - notif: impl for<'a> AsyncFn(Box + 'a>) -> io::Result<()>, - req: impl for<'a> AsyncFn(Box + 'a>) -> io::Result>, -) -> (impl Client + 'static, CommCtx, impl Future>) { +) -> (impl Client + 'static, CommCtx, IoCommServer) { let i = Rc::new(i); let (onsub, client) = IoClient::new(o.clone()); let (exit, onexit) = channel(1); - (client, CommCtx { exit }, async move { + (client, CommCtx { exit }, IoCommServer { o, i, onsub, onexit }) +} +pub struct IoCommServer { + o: Rc>>>, + i: Rc>>>, + onsub: Receiver, + onexit: Receiver<()>, +} +impl IoCommServer { + pub async fn listen( + self, + notif: impl for<'a> AsyncFn(Box + 'a>) -> io::Result<()>, + req: impl for<'a> AsyncFn(Box + 'a>) -> io::Result>, + ) -> io::Result<()> { + let Self { o, i, onexit, onsub } = self; enum Event { Input(u64, IoGuard), Sub(ReplySub), @@ -363,6 +374,9 @@ pub fn io_comm( Err(e) => break 'body Err(e), Ok(Event::Exit) => { *exiting.borrow_mut() = true; + let mut out = o.lock().await; + out.as_mut().flush().await?; + out.as_mut().close().await?; break; }, Ok(Event::Sub(ReplySub { id, ack, cb })) => { @@ -399,11 +413,12 @@ pub fn io_comm( next? } Ok(()) - }) + } } #[cfg(test)] mod test { + use std::cell::RefCell; use std::rc::Rc; use futures::channel::mpsc; @@ -414,6 +429,8 @@ mod test { use test_executors::spin_on; use unsync_pipe::pipe; + use crate::logging::test::TestLogger; + use crate::logging::with_logger; use crate::reqnot::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm}; #[derive(Clone, Debug, PartialEq, Coding, Hierarchy)] @@ -422,33 +439,36 @@ mod test { #[test] fn notification() { - spin_on(async { + let logger = TestLogger::new(async |s| eprint!("{s}")); + spin_on(with_logger(logger, async { let (in1, out2) = pipe(1024); let (in2, out1) = pipe(1024); let (received, mut on_receive) = mpsc::channel(2); - let (_, recv_ctx, run_recv) = io_comm( - Rc::new(Mutex::new(Box::pin(in2))), - Mutex::new(Box::pin(out2)), - async |notif| { - received.clone().send(notif.read::().await?).await.unwrap(); - Ok(()) + let (_, recv_ctx, recv_srv) = + io_comm(Rc::new(Mutex::new(Box::pin(in2))), Mutex::new(Box::pin(out2))); + let (sender, ..) = io_comm(Rc::new(Mutex::new(Box::pin(in1))), Mutex::new(Box::pin(out1))); + join!( + async { + recv_srv + .listen( + async |notif| { + received.clone().send(notif.read::().await?).await.unwrap(); + Ok(()) + }, + async |_| panic!("Should receive notif, not request"), + ) + .await + .unwrap() }, - async |_| panic!("Should receive notif, not request"), + async { + sender.notify(TestNotif(3)).await.unwrap(); + assert_eq!(on_receive.next().await, Some(TestNotif(3))); + sender.notify(TestNotif(4)).await.unwrap(); + assert_eq!(on_receive.next().await, Some(TestNotif(4))); + recv_ctx.exit().await; + } ); - let (sender, ..) = io_comm( - Rc::new(Mutex::new(Box::pin(in1))), - Mutex::new(Box::pin(out1)), - async |_| panic!("Should not receive notif"), - async |_| panic!("Should not receive request"), - ); - join!(async { run_recv.await.unwrap() }, async { - sender.notify(TestNotif(3)).await.unwrap(); - assert_eq!(on_receive.next().await, Some(TestNotif(3))); - sender.notify(TestNotif(4)).await.unwrap(); - assert_eq!(on_receive.next().await, Some(TestNotif(4))); - recv_ctx.exit().await; - }); - }) + })) } #[derive(Clone, Debug, Coding, Hierarchy)] @@ -460,30 +480,94 @@ mod test { #[test] fn request() { - spin_on(async { + let logger = TestLogger::new(async |s| eprint!("{s}")); + spin_on(with_logger(logger, async { let (in1, out2) = pipe(1024); let (in2, out1) = pipe(1024); - let (_, srv_ctx, run_srv) = io_comm( - Rc::new(Mutex::new(Box::pin(in2))), - Mutex::new(Box::pin(out2)), - async |_| panic!("No notifs expected"), - async |mut req| { - let val = req.read_req::().await?; - req.reply(&val, &(val.0 + 1)).await + let (_, srv_ctx, srv) = + io_comm(Rc::new(Mutex::new(Box::pin(in2))), Mutex::new(Box::pin(out2))); + let (client, client_ctx, client_srv) = + io_comm(Rc::new(Mutex::new(Box::pin(in1))), Mutex::new(Box::pin(out1))); + join!( + async { + srv + .listen( + async |_| panic!("No notifs expected"), + async |mut req| { + let val = req.read_req::().await?; + req.reply(&val, &(val.0 + 1)).await + }, + ) + .await + .unwrap() }, + async { + client_srv + .listen( + async |_| panic!("Not expecting ingress notif"), + async |_| panic!("Not expecting ingress req"), + ) + .await + .unwrap() + }, + async { + let response = client.request(DummyRequest(5)).await.unwrap(); + assert_eq!(response, 6); + srv_ctx.exit().await; + client_ctx.exit().await; + } ); - let (client, client_ctx, run_client) = io_comm( - Rc::new(Mutex::new(Box::pin(in1))), - Mutex::new(Box::pin(out1)), - async |_| panic!("Not expecting ingress notif"), - async |_| panic!("Not expecting ingress req"), - ); - join!(async { run_srv.await.unwrap() }, async { run_client.await.unwrap() }, async { - let response = client.request(DummyRequest(5)).await.unwrap(); - assert_eq!(response, 6); - srv_ctx.exit().await; - client_ctx.exit().await; - }); - }) + })) + } + + #[test] + fn exit() { + let logger = TestLogger::new(async |s| eprint!("{s}")); + spin_on(with_logger(logger, async { + let (input1, output1) = pipe(1024); + let (input2, output2) = pipe(1024); + let (reply_client, reply_context, reply_server) = + io_comm(Rc::new(Mutex::new(Box::pin(input1))), Mutex::new(Box::pin(output2))); + let (req_client, req_context, req_server) = + io_comm(Rc::new(Mutex::new(Box::pin(input2))), Mutex::new(Box::pin(output1))); + let reply_context = RefCell::new(Some(reply_context)); + let (exit, onexit) = futures::channel::oneshot::channel::<()>(); + join!( + async move { + reply_server + .listen( + async |hand| { + let _notif = hand.read::().await.unwrap(); + let context = reply_context.borrow_mut().take().unwrap(); + context.exit().await; + Ok(()) + }, + async |mut hand| { + let req = hand.read_req::().await?; + hand.reply(&req, &(req.0 + 1)).await + }, + ) + .await + .unwrap(); + exit.send(()).unwrap(); + let _client = reply_client; + }, + async move { + req_server + .listen( + async |_| panic!("Only the other server expected notifs"), + async |_| panic!("Only the other server expected requests"), + ) + .await + .unwrap(); + let _ctx = req_context; + }, + async move { + req_client.request(DummyRequest(0)).await.unwrap(); + req_client.notify(TestNotif(0)).await.unwrap(); + onexit.await.unwrap(); + } + ) + })); } } diff --git a/orchid-extension/Cargo.toml b/orchid-extension/Cargo.toml index cfaddf7..7a7147a 100644 --- a/orchid-extension/Cargo.toml +++ b/orchid-extension/Cargo.toml @@ -11,15 +11,15 @@ async-once-cell = "0.5.4" bound = "0.6.0" derive_destructure = "1.0.0" dyn-clone = "1.0.20" -futures = { version = "0.3.31", features = [ +futures = { version = "0.3.31", default-features = false, features = [ "std", "async-await", -], default-features = false } +] } futures-locks = "0.7.1" -hashbrown = "0.16.0" +hashbrown = "0.16.1" include_dir = { version = "0.7.4", optional = true } itertools = "0.14.0" -konst = "0.4.2" +konst = "0.4.3" lazy_static = "1.5.0" memo-map = "0.3.3" never = "0.1.0" @@ -28,12 +28,12 @@ orchid-api = { version = "0.1.0", path = "../orchid-api" } orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" } orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" } orchid-base = { version = "0.1.0", path = "../orchid-base" } -ordered-float = "5.0.0" -pastey = "0.1.1" +ordered-float = "5.1.0" +pastey = "0.2.1" substack = "1.1.1" task-local = "0.1.0" -tokio = { version = "1.47.1", optional = true, features = [] } -tokio-util = { version = "0.7.16", optional = true, features = ["compat"] } +tokio = { version = "1.49.0", optional = true, features = [] } +tokio-util = { version = "0.7.17", optional = true, features = ["compat"] } trait-set = "0.3.0" diff --git a/orchid-extension/src/atom_owned.rs b/orchid-extension/src/atom_owned.rs index 09cd966..6bb9fd3 100644 --- a/orchid-extension/src/atom_owned.rs +++ b/orchid-extension/src/atom_owned.rs @@ -19,6 +19,7 @@ use never::Never; use orchid_api_traits::{Decode, Encode, enc_vec}; use orchid_base::error::OrcRes; use orchid_base::format::{FmtCtx, FmtCtxImpl, FmtUnit, take_first}; +use orchid_base::logging::log; use orchid_base::name::Sym; use task_local::task_local; @@ -338,5 +339,5 @@ pub async fn debug_print_obj_store(show_atoms: bool) { message += &format!("\n{k:?} -> {}", take_first(&atom.dyn_print().await, true)); } } - eprintln!("{message}") + writeln!(log("debug"), "{message}").await } diff --git a/orchid-extension/src/atom_thin.rs b/orchid-extension/src/atom_thin.rs index 5817b7d..5d9e729 100644 --- a/orchid-extension/src/atom_thin.rs +++ b/orchid-extension/src/atom_thin.rs @@ -8,7 +8,7 @@ use futures::{AsyncRead, AsyncWrite, FutureExt}; use orchid_api_traits::{Coding, enc_vec}; use orchid_base::error::OrcRes; use orchid_base::format::FmtUnit; -use orchid_base::logging::logger; +use orchid_base::logging::log; use orchid_base::name::Sym; use crate::api; @@ -89,7 +89,7 @@ impl AtomDynfo for ThinAtomDynfo { fn drop<'a>(&'a self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, ()> { Box::pin(async move { let string_self = T::decode_slice(&mut &buf[..]).print().await; - writeln!(logger(), "Received drop signal for non-drop atom {string_self:?}"); + writeln!(log("warn"), "Received drop signal for non-drop atom {string_self:?}").await; }) } } diff --git a/orchid-extension/src/binary.rs b/orchid-extension/src/binary.rs new file mode 100644 index 0000000..d870f3f --- /dev/null +++ b/orchid-extension/src/binary.rs @@ -0,0 +1,30 @@ +use std::rc::Rc; + +use futures::future::LocalBoxFuture; +use orchid_base::binary::future_to_vt; + +use crate::api; +use crate::entrypoint::ExtensionBuilder; +use crate::ext_port::ExtPort; + +pub type ExtCx = api::binary::ExtensionContext; + +struct Spawner(api::binary::Spawner); +impl Drop for Spawner { + fn drop(&mut self) { (self.0.drop)(self.0.data) } +} +impl Spawner { + pub fn spawn(&self, fut: LocalBoxFuture<'static, ()>) { + (self.0.spawn)(self.0.data, future_to_vt(fut)) + } +} + +pub fn orchid_extension_main_body(cx: ExtCx, builder: ExtensionBuilder) { + let spawner = Spawner(cx.spawner); + builder.build(ExtPort { + input: Box::pin(cx.input), + output: Box::pin(cx.output), + log: Box::pin(cx.log), + spawn: Rc::new(move |fut| spawner.spawn(fut)), + }); +} diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index 1e3a77f..0c52271 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -15,7 +15,7 @@ use orchid_api_traits::{Decode, Encode, Request, UnderRoot, enc_vec}; use orchid_base::char_filter::{char_filter_match, char_filter_union, mk_char_filter}; use orchid_base::error::try_with_reporter; use orchid_base::interner::{es, is, with_interner}; -use orchid_base::logging::{Logger, with_logger}; +use orchid_base::logging::{log, with_logger}; use orchid_base::name::Sym; use orchid_base::parse::{Comment, Snippet}; use orchid_base::reqnot::{ @@ -35,6 +35,7 @@ use crate::ext_port::ExtPort; use crate::func_atom::with_funs_ctx; use crate::interner::new_interner; use crate::lexer::{LexContext, ekey_cascade, ekey_not_applicable}; +use crate::logger::LoggerImpl; use crate::parser::{PTokTree, ParsCtx, get_const, linev_into_api, with_parsed_const_ctx}; use crate::reflection::with_refl_roots; use crate::system::{SysCtx, atom_by_idx, cted, with_sys}; @@ -58,9 +59,17 @@ pub async fn with_comm(c: Rc, ctx: CommCtx, fut: F) -> F: CLIENT.scope(c, CTX.scope(Rc::new(RefCell::new(Some(ctx))), fut)).await } +task_local! { + pub static MUTE_REPLY: (); +} + /// Send a request through the global client's [ClientExt::request] pub async fn request>(t: T) -> T::Response { - get_client().request(t).await.unwrap() + let response = get_client().request(t).await.unwrap(); + if MUTE_REPLY.try_with(|b| *b).is_err() { + writeln!(log("msg"), "Got response {response:?}").await; + } + response } /// Send a notification through the global client's [ClientExt::notify] @@ -79,15 +88,7 @@ task_local! { } async fn with_sys_record(id: api::SysId, fut: F) -> F::Output { - let cted = SYSTEM_TABLE.with(|tbl| { - eprintln!( - "Existing systems are {}", - tbl.borrow().iter().map(|(k, v)| format!("{k:?}={:?}", v.cted)).join(";") - ); - let sys = tbl.borrow().get(&id).expect("Invalid sys ID").cted.clone(); - eprintln!("Selected {:?}", sys); - sys - }); + let cted = SYSTEM_TABLE.with(|tbl| tbl.borrow().get(&id).expect("Invalid sys ID").cted.clone()); with_sys(SysCtx(id, cted), fut).await } @@ -126,8 +127,7 @@ impl ExtensionBuilder { self.add_context(with_lazy_member_store); self.add_context(with_refl_roots); (ctx.spawn)(Box::pin(async move { - let api::HostHeader { log_strategy, msg_logs } = - api::HostHeader::decode(ctx.input.as_mut()).await.unwrap(); + let host_header = api::HostHeader::decode(ctx.input.as_mut()).await.unwrap(); let decls = (self.systems.iter().enumerate()) .map(|(id, sys)| (u16::try_from(id).expect("more than u16max system ctors"), sys)) .map(|(id, sys)| sys.decl(api::SysDeclId(NonZero::new(id + 1).unwrap()))) @@ -137,24 +137,26 @@ impl ExtensionBuilder { .await .unwrap(); ctx.output.as_mut().flush().await.unwrap(); - let logger = Logger::new(log_strategy); - let logger2 = logger.clone(); - let msg_logger = Logger::new(msg_logs); - let (client, ctx, run_extension) = io_comm( - Rc::new(Mutex::new(ctx.output)), - Mutex::new(ctx.input), - async move |n: Box>| { - match n.read().await.unwrap() { + let logger1 = LoggerImpl::from_api(&host_header.logger); + let logger2 = logger1.clone(); + let (client, comm_ctx, extension_srv) = + io_comm(Rc::new(Mutex::new(ctx.output)), Mutex::new(ctx.input)); + let extension_fut = extension_srv.listen( + async |n: Box>| { + let notif = n.read().await.unwrap(); + match notif { api::HostExtNotif::Exit => exit().await, } Ok(()) }, - async move |mut reader| { + async |mut reader| { with_stash(async { let req = reader.read_req().await.unwrap(); let handle = reader.finish().await; + // Atom printing is never reported because it generates too much + // noise if !matches!(req, api::HostExtReq::AtomReq(api::AtomReq::AtomPrint(_))) { - writeln!(msg_logger, "{} extension received request {req:?}", self.name); + writeln!(log("msg"), "{} extension received request {req:?}", self.name).await; } match req { api::HostExtReq::SystemDrop(sys_drop) => { @@ -266,11 +268,6 @@ impl ExtensionBuilder { let ekey_na = ekey_not_applicable().await; let ekey_cascade = ekey_cascade().await; let lexers = cted().inst().dyn_lexers(); - writeln!( - logger, - "sys={sys:?}, tc={trigger_char}, lexers={}", - lexers.iter().map(|l| format!("{l:?}")).join(",") - ); for lx in lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char)) { @@ -290,7 +287,7 @@ impl ExtensionBuilder { }, } } - writeln!(logger, "Got notified about n/a character '{trigger_char}'"); + writeln!(log("warn"), "Got notified about n/a character '{trigger_char}'").await; expr_store.dispose().await; handle.reply(&lex, &None).await }) @@ -423,16 +420,16 @@ impl ExtensionBuilder { logger2, with_comm( Rc::new(client), - ctx, + comm_ctx, (self.context.into_iter()).fold( - Box::pin(async move { run_extension.await.unwrap() }) as LocalBoxFuture<()>, + Box::pin(async { extension_fut.await.unwrap() }) as LocalBoxFuture<()>, |fut, cx| cx.apply(fut), ), ), ), ), ) - .await + .await; }) as Pin>); } } diff --git a/orchid-extension/src/interner.rs b/orchid-extension/src/interner.rs index cd90006..9e76ca1 100644 --- a/orchid-extension/src/interner.rs +++ b/orchid-extension/src/interner.rs @@ -6,7 +6,7 @@ use orchid_base::interner::local_interner::{Int, StrBranch, StrvBranch}; use orchid_base::interner::{IStr, IStrv, InternerSrv}; use crate::api; -use crate::entrypoint::request; +use crate::entrypoint::{MUTE_REPLY, request}; #[derive(Default)] struct ExtInterner { @@ -17,7 +17,9 @@ impl InternerSrv for ExtInterner { fn is<'a>(&'a self, v: &'a str) -> LocalBoxFuture<'a, IStr> { match self.str.i(v) { Ok(i) => Box::pin(ready(i)), - Err(e) => Box::pin(async { e.set_if_empty(request(api::InternStr(v.to_owned())).await) }), + Err(e) => Box::pin(async { + e.set_if_empty(MUTE_REPLY.scope((), request(api::InternStr(v.to_owned()))).await) + }), } } fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr> { diff --git a/orchid-extension/src/lib.rs b/orchid-extension/src/lib.rs index 86bf5b2..94ef2b8 100644 --- a/orchid-extension/src/lib.rs +++ b/orchid-extension/src/lib.rs @@ -12,6 +12,7 @@ pub mod func_atom; pub mod gen_expr; pub mod interner; pub mod lexer; +pub mod logger; pub mod other_system; pub mod parser; pub mod reflection; @@ -19,3 +20,4 @@ pub mod system; pub mod system_ctor; pub mod tokio; pub mod tree; +pub mod binary; diff --git a/orchid-extension/src/logger.rs b/orchid-extension/src/logger.rs new file mode 100644 index 0000000..ee8cc5b --- /dev/null +++ b/orchid-extension/src/logger.rs @@ -0,0 +1,57 @@ +use std::fmt::Arguments; +use std::fs::File; +use std::io::Write; +use std::rc::Rc; + +use futures::future::LocalBoxFuture; +use hashbrown::HashMap; +use orchid_base::interner::is; +use orchid_base::logging::{LogWriter, Logger}; + +use crate::api; +use crate::entrypoint::notify; + +pub struct LogWriterImpl { + category: String, + strat: api::LogStrategy, +} +impl LogWriter for LogWriterImpl { + fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> { + Box::pin(async move { + match &self.strat { + api::LogStrategy::Discard => (), + api::LogStrategy::Default => + notify(api::Log { category: is(&self.category).await.to_api(), message: fmt.to_string() }) + .await, + api::LogStrategy::File { path, .. } => { + let mut file = (File::options().write(true).create(true).truncate(false).open(path)) + .unwrap_or_else(|e| panic!("Could not open {path}: {e}")); + file.write_fmt(fmt).unwrap_or_else(|e| panic!("Could not write to {path}: {e}")); + }, + } + }) + } +} + +#[derive(Clone)] +pub struct LoggerImpl { + default: Option, + routing: HashMap, +} +impl LoggerImpl { + pub fn from_api(api: &api::Logger) -> Self { + Self { + default: api.default.clone(), + routing: api.routing.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), + } + } +} +impl Logger for LoggerImpl { + fn writer(&self, category: &str) -> Rc { + Rc::new(LogWriterImpl { category: category.to_string(), strat: self.strat(category) }) + } + fn strat(&self, category: &str) -> orchid_api::LogStrategy { + (self.routing.get(category).cloned().or(self.default.clone())) + .expect("Unrecognized log category with no default strategy") + } +} diff --git a/orchid-extension/src/system_ctor.rs b/orchid-extension/src/system_ctor.rs index c5bcd61..d2008e8 100644 --- a/orchid-extension/src/system_ctor.rs +++ b/orchid-extension/src/system_ctor.rs @@ -64,8 +64,7 @@ pub trait SystemCtor: Debug + Send + Sync + 'static { type Instance: System; const NAME: &'static str; const VERSION: f64; - /// Create a system instance. When this function is called, a context object - /// isn't yet available + /// Create a system instance. fn inst(&self, deps: ::Sat) -> Self::Instance; } diff --git a/orchid-extension/src/tokio.rs b/orchid-extension/src/tokio.rs index 0c9859b..33a856f 100644 --- a/orchid-extension/src/tokio.rs +++ b/orchid-extension/src/tokio.rs @@ -2,9 +2,15 @@ use std::rc::Rc; use crate::entrypoint::ExtensionBuilder; use crate::ext_port::ExtPort; - +/// Run an extension inside a Tokio localset. Since the extension API does not +/// provide a forking mechanism, it can safely abort once the localset is +/// exhausted. If an extension absolutely needs a parallel thread, it can import +/// and call [tokio::task::spawn_local] which will keep alive the localset and +/// postpone the aggressive shutdown, and listen for the [Drop::drop] of the +/// value returned by [crate::system_ctor::SystemCtor::inst] to initiate +/// shutdown. #[cfg(feature = "tokio")] -pub async fn tokio_main(builder: ExtensionBuilder) { +pub async fn tokio_main(builder: ExtensionBuilder) -> ! { use tokio::io::{stderr, stdin, stdout}; use tokio::task::{LocalSet, spawn_local}; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; @@ -21,4 +27,5 @@ pub async fn tokio_main(builder: ExtensionBuilder) { }); }); local_set.await; + std::process::exit(0) } diff --git a/orchid-host/Cargo.toml b/orchid-host/Cargo.toml index 151cbc7..57e990c 100644 --- a/orchid-host/Cargo.toml +++ b/orchid-host/Cargo.toml @@ -8,22 +8,28 @@ edition = "2024" [dependencies] async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" } async-once-cell = "0.5.4" -async-process = "2.4.0" bound = "0.6.0" derive_destructure = "1.0.0" futures = { version = "0.3.31", features = ["std"], default-features = false } futures-locks = "0.7.1" -hashbrown = "0.16.0" +hashbrown = "0.16.1" itertools = "0.14.0" lazy_static = "1.5.0" +libloading = { version = "0.9.0", optional = true } memo-map = "0.3.3" never = "0.1.0" num-traits = "0.2.19" orchid-api = { version = "0.1.0", path = "../orchid-api" } orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" } orchid-base = { version = "0.1.0", path = "../orchid-base" } -ordered-float = "5.0.0" -pastey = "0.1.1" +ordered-float = "5.1.0" +pastey = "0.2.1" substack = "1.1.1" -test_executors = "0.3.5" +test_executors = "0.4.1" +tokio = { version = "1.49.0", features = ["process"], optional = true } +tokio-util = { version = "0.7.18", features = ["compat"], optional = true } trait-set = "0.3.0" +unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" } + +[features] +tokio = ["dep:tokio", "dep:tokio-util", "dep:libloading"] diff --git a/orchid-host/src/ctx.rs b/orchid-host/src/ctx.rs index 6e7779c..5269a3e 100644 --- a/orchid-host/src/ctx.rs +++ b/orchid-host/src/ctx.rs @@ -6,10 +6,10 @@ use std::{fmt, ops}; use futures::future::LocalBoxFuture; use futures_locks::RwLock; use hashbrown::HashMap; -use orchid_base::logging::Logger; use crate::api; use crate::expr_store::ExprStore; +use crate::logger::LoggerImpl; use crate::system::{System, WeakSystem}; use crate::tree::WeakRoot; @@ -24,11 +24,11 @@ pub trait Spawner { pub struct CtxData { spawner: Rc, - pub msg_logs: Logger, pub systems: RwLock>, pub system_id: RefCell, pub exprs: ExprStore, pub root: RwLock, + pub logger: LoggerImpl, } #[derive(Clone)] pub struct Ctx(Rc); @@ -46,14 +46,14 @@ impl WeakCtx { } impl Ctx { #[must_use] - pub fn new(msg_logs: Logger, spawner: impl Spawner + 'static) -> Self { + pub fn new(spawner: impl Spawner + 'static, logger: LoggerImpl) -> Self { Self(Rc::new(CtxData { - msg_logs, spawner: Rc::new(spawner), systems: RwLock::default(), system_id: RefCell::new(NonZero::new(1).unwrap()), exprs: ExprStore::default(), root: RwLock::default(), + logger, })) } /// Spawn a parallel future that you can join at any later time. diff --git a/orchid-host/src/dylib.rs b/orchid-host/src/dylib.rs new file mode 100644 index 0000000..376b9d2 --- /dev/null +++ b/orchid-host/src/dylib.rs @@ -0,0 +1,55 @@ +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +use hashbrown::HashMap; +use libloading::Library; +use orchid_base::binary::vt_to_future; + +use crate::api; +use crate::ctx::Ctx; +use crate::extension::ExtPort; + +static DYNAMIC_LIBRARIES: Mutex>>> = Mutex::new(None); +fn load_dylib(path: &Path) -> Result, libloading::Error> { + let mut g = DYNAMIC_LIBRARIES.lock().unwrap(); + let map = g.get_or_insert_default(); + if let Some(lib) = map.get(path) { + Ok(lib.clone()) + } else { + let lib = Arc::new(unsafe { Library::new(path) }?); + map.insert(path.to_owned(), lib.clone()); + Ok(lib) + } +} + +#[cfg(feature = "tokio")] +pub async fn ext_dylib(path: &Path, ctx: Ctx) -> Result { + use futures::io::BufReader; + use futures::{AsyncBufReadExt, StreamExt}; + use libloading::Symbol; + use unsync_pipe::pipe; + + let (write_input, input) = pipe(1024); + let (output, read_output) = pipe(1024); + let (log, read_log) = pipe(1024); + let log_path = path.to_string_lossy().to_string(); + let _ = ctx.spawn(async move { + use orchid_base::logging::log; + let mut lines = BufReader::new(read_log).lines(); + while let Some(line) = lines.next().await { + writeln!(log("stderr"), "{log_path} err> {}", line.expect("Readline implies this")).await; + } + }); + let library = load_dylib(path)?; + let entrypoint: Symbol = + unsafe { library.get("orchid_extension_main") }?; + let data = Box::into_raw(Box::new(ctx)) as *const (); + extern "C" fn drop(data: *const ()) { std::mem::drop(unsafe { Box::from_raw(data as *mut Ctx) }) } + extern "C" fn spawn(data: *const (), vt: api::binary::FutureVT) { + let _ = unsafe { (data as *mut Ctx).as_mut().unwrap().spawn(vt_to_future(vt)) }; + } + let spawner = api::binary::Spawner { data, drop, spawn }; + let cx = api::binary::ExtensionContext { input, output, log, spawner }; + unsafe { (entrypoint)(cx) }; + Ok(ExtPort { input: Box::pin(write_input), output: Box::pin(read_output) }) +} diff --git a/orchid-host/src/execute.rs b/orchid-host/src/execute.rs index 10974a2..4276f04 100644 --- a/orchid-host/src/execute.rs +++ b/orchid-host/src/execute.rs @@ -6,7 +6,7 @@ use futures_locks::{RwLockWriteGuard, TryLockError}; use orchid_base::error::OrcErrv; use orchid_base::format::fmt; use orchid_base::location::Pos; -use orchid_base::logging::logger; +use orchid_base::logging::log; use crate::expr::{Expr, ExprKind, PathSet, Step}; use crate::tree::Root; @@ -86,7 +86,7 @@ impl ExecCtx { while self.use_gas(1) { let mut kind_swap = ExprKind::Missing; mem::swap(&mut kind_swap, &mut self.cur); - writeln!(logger(), "Exxecute lvl{} {}", self.stack.len(), fmt(&kind_swap).await); + writeln!(log("debug"), "Exxecute lvl{} {}", self.stack.len(), fmt(&kind_swap).await).await; let (kind, op) = match kind_swap { ExprKind::Identity(target) => { let inner = self.unpack_ident(&target).await; diff --git a/orchid-host/src/extension.rs b/orchid-host/src/extension.rs index 2a4924d..d06116b 100644 --- a/orchid-host/src/extension.rs +++ b/orchid-host/src/extension.rs @@ -14,11 +14,10 @@ use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, SinkExt, StreamExt}; use hashbrown::{HashMap, HashSet}; use itertools::Itertools; use orchid_api_traits::{Decode, Encode, Request}; -use orchid_base::clone; use orchid_base::format::{FmtCtxImpl, Format}; use orchid_base::interner::{IStr, IStrv, es, ev, is, iv}; use orchid_base::location::Pos; -use orchid_base::logging::logger; +use orchid_base::logging::log; use orchid_base::name::Sym; use orchid_base::reqnot::{Client, ClientExt, MsgReaderExt, ReqHandleExt, ReqReaderExt, io_comm}; use orchid_base::stash::{stash, with_stash}; @@ -70,199 +69,213 @@ impl Drop for ExtensionData { pub struct Extension(Rc); impl Extension { pub async fn new(mut init: ExtPort, ctx: Ctx) -> io::Result { - api::HostHeader { log_strategy: logger().strat(), msg_logs: ctx.msg_logs.strat() } - .encode(init.input.as_mut()) - .await - .unwrap(); + api::HostHeader { logger: ctx.logger.to_api() }.encode(init.input.as_mut()).await.unwrap(); init.input.flush().await.unwrap(); let header = api::ExtensionHeader::decode(init.output.as_mut()).await.unwrap(); + let header2 = header.clone(); Ok(Self(Rc::new_cyclic(|weak: &Weak| { // context not needed because exit is extension-initiated - let (client, _, future) = io_comm( - Rc::new(Mutex::new(init.input)), - Mutex::new(init.output), - clone!(weak; async move |reader| { - with_stash(async { - let this = Extension(weak.upgrade().unwrap()); - let notif = reader.read::().await.unwrap(); - if !matches!(notif, api::ExtHostNotif::Log(_)) { - writeln!(logger(), "Host received notif {notif:?}"); - } - match notif { - api::ExtHostNotif::ExprNotif(api::ExprNotif::Acquire(acq)) => { - let target = this.0.ctx.exprs.get_expr(acq.1).expect("Invalid ticket"); - this.0.ctx.exprs.give_expr(target) - } - api::ExtHostNotif::ExprNotif(api::ExprNotif::Release(rel)) => { - if this.is_own_sys(rel.0).await { - this.0.ctx.exprs.take_expr(rel.1); - } else { - writeln!(this.0.ctx.msg_logs, "Not our system {:?}", rel.0) + let (client, _, comm) = io_comm(Rc::new(Mutex::new(init.input)), Mutex::new(init.output)); + let weak2 = weak; + let weak = weak.clone(); + let ctx2 = ctx.clone(); + let join_ext = ctx.clone().spawn(async move { + comm + .listen( + async |reader| { + with_stash(async { + let this = Extension(weak.upgrade().unwrap()); + let notif = reader.read::().await.unwrap(); + // logging is never logged because its value will be logged anyway + if !matches!(notif, api::ExtHostNotif::Log(_)) { + writeln!(log("msg"), "Host received notif {notif:?}").await; } - }, - api::ExtHostNotif::Log(api::Log(str)) => logger().log(str), - api::ExtHostNotif::Sweeped(data) => { - for i in join_all(data.strings.into_iter().map(es)).await { - this.0.strings.borrow_mut().remove(&i); - } - for i in join_all(data.vecs.into_iter().map(ev)).await { - this.0.string_vecs.borrow_mut().remove(&i); - } - }, - } - Ok(()) - }).await - }), - { - clone!(weak, ctx); - async move |mut reader| { - with_stash(async { - let this = Self(weak.upgrade().unwrap()); - let req = reader.read_req::().await.unwrap(); - let handle = reader.finish().await; - if !matches!(req, api::ExtHostReq::ExtAtomPrint(_)) { - writeln!(logger(), "Host received request {req:?}"); - } - match req { - api::ExtHostReq::Ping(ping) => handle.reply(&ping, &()).await, - api::ExtHostReq::IntReq(intreq) => match intreq { - api::IntReq::InternStr(s) => { - let i = is(&s.0).await; - this.0.strings.borrow_mut().insert(i.clone()); - handle.reply(&s, &i.to_api()).await + match notif { + api::ExtHostNotif::ExprNotif(api::ExprNotif::Acquire(acq)) => { + let target = this.0.ctx.exprs.get_expr(acq.1).expect("Invalid ticket"); + this.0.ctx.exprs.give_expr(target) }, - api::IntReq::InternStrv(v) => { - let tokens = join_all(v.0.iter().map(|m| es(*m))).await; - this.0.strings.borrow_mut().extend(tokens.iter().cloned()); - let i = iv(&tokens).await; - this.0.string_vecs.borrow_mut().insert(i.clone()); - handle.reply(&v, &i.to_api()).await - }, - api::IntReq::ExternStr(si) => { - let i = es(si.0).await; - this.0.strings.borrow_mut().insert(i.clone()); - handle.reply(&si, &i.to_string()).await - }, - api::IntReq::ExternStrv(vi) => { - let i = ev(vi.0).await; - this.0.strings.borrow_mut().extend(i.iter().cloned()); - this.0.string_vecs.borrow_mut().insert(i.clone()); - let markerv = i.iter().map(|t| t.to_api()).collect_vec(); - handle.reply(&vi, &markerv).await - }, - }, - api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => { - let sys = ctx.system_inst(atom.owner).await.expect("owner of live atom dropped"); - let client = sys.client(); - let reply = - client.request(api::Fwded(fw.0.clone(), *key, body.clone())).await.unwrap(); - handle.reply(fw, &reply).await - }, - api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => { - let sys = ctx.system_inst(id).await.unwrap(); - handle.reply(fw, &sys.request(body.clone()).await).await - }, - api::ExtHostReq::SubLex(sl) => { - let (rep_in, mut rep_out) = channel(0); - { - let lex_g = this.0.lex_recur.lock().await; - let mut req_in = - lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid"); - req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap(); - } - handle.reply(&sl, &rep_out.next().await.unwrap()).await - }, - api::ExtHostReq::ExprReq(expr_req) => match expr_req { - api::ExprReq::Inspect(ins @ api::Inspect { target }) => { - let expr = ctx.exprs.get_expr(target).expect("Invalid ticket"); - handle - .reply(&ins, &api::Inspected { - refcount: expr.strong_count() as u32, - location: expr.pos().to_api(), - kind: expr.to_api().await, - }) - .await - }, - api::ExprReq::Create(ref cre @ api::Create(ref expr)) => { - let expr = Expr::from_api(expr, PathSetBuilder::new(), ctx.clone()).await; - let expr_id = expr.id(); - ctx.exprs.give_expr(expr); - handle.reply(cre, &expr_id).await - }, - }, - api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => { - let reply: ::Response = 'reply: { - let path = ev(path).await; - let root = (ctx.root.read().await.upgrade()) - .expect("LSModule called when root isn't in context"); - let root_data = &*root.0.read().await; - let mut walk_ctx = (ctx.clone(), &root_data.consts); - let module = - match walk(&root_data.root, false, path.iter().cloned(), &mut walk_ctx).await - { - Ok(module) => module, - Err(ChildError { kind, .. }) => - break 'reply Err(match kind { - ChildErrorKind::Private => panic!("Access checking was disabled"), - ChildErrorKind::Constant => api::LsModuleError::IsConstant, - ChildErrorKind::Missing => api::LsModuleError::InvalidPath, - }), - }; - let mut members = std::collections::HashMap::new(); - for (k, v) in &module.members { - let kind = match v.kind(ctx.clone(), &root_data.consts).await { - MemberKind::Const => api::MemberInfoKind::Constant, - MemberKind::Module(_) => api::MemberInfoKind::Module, - }; - members.insert(k.to_api(), api::MemberInfo { public: v.public, kind }); + api::ExtHostNotif::ExprNotif(api::ExprNotif::Release(rel)) => { + if this.is_own_sys(rel.0).await { + this.0.ctx.exprs.take_expr(rel.1); + } else { + writeln!(log("warn"), "Not our system {:?}", rel.0).await } - Ok(api::ModuleInfo { members }) - }; - handle.reply(ls, &reply).await - }, - api::ExtHostReq::ResolveNames(ref rn) => { - let api::ResolveNames { constid, names, sys } = rn; - let mut resolver = { - let systems = ctx.systems.read().await; - let weak_sys = systems.get(sys).expect("ResolveNames for invalid sys"); - let sys = weak_sys.upgrade().expect("ResolveNames after sys drop"); - sys.name_resolver(*constid).await - }; - let responses = stream(async |mut cx| { - for name in names { - cx.emit(match resolver(&ev(*name).await[..]).await { - Ok(abs) => Ok(abs.to_sym().await.to_api()), - Err(e) => Err(e.to_api()), - }) - .await + }, + api::ExtHostNotif::Log(api::Log { category, message }) => + write!(log(&es(category).await), "{message}").await, + api::ExtHostNotif::Sweeped(data) => { + for i in join_all(data.strings.into_iter().map(es)).await { + this.0.strings.borrow_mut().remove(&i); } - }) - .collect() - .await; - handle.reply(rn, &responses).await - }, - api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => { - let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await; - let unit = atom.print(&FmtCtxImpl::default()).await; - handle.reply(eap, &unit.to_api()).await - }, - } - }) - .await - } - }, - ); - - let join_ext = ctx.spawn(async { - future.await.unwrap(); - // extension exited successfully + for i in join_all(data.vecs.into_iter().map(ev)).await { + this.0.string_vecs.borrow_mut().remove(&i); + } + }, + } + Ok(()) + }) + .await + }, + async |mut reader| { + with_stash(async { + let req = reader.read_req::().await.unwrap(); + let handle = reader.finish().await; + // Atom printing and interning is never reported because it generates too much + // noise + if !matches!(req, api::ExtHostReq::ExtAtomPrint(_)) + || matches!(req, api::ExtHostReq::IntReq(_)) + { + writeln!(log("msg"), "Host received request {req:?}").await; + } + let this = Self(weak.upgrade().unwrap()); + match req { + api::ExtHostReq::Ping(ping) => handle.reply(&ping, &()).await, + api::ExtHostReq::IntReq(intreq) => match intreq { + api::IntReq::InternStr(s) => { + let i = is(&s.0).await; + this.0.strings.borrow_mut().insert(i.clone()); + handle.reply(&s, &i.to_api()).await + }, + api::IntReq::InternStrv(v) => { + let tokens = join_all(v.0.iter().map(|m| es(*m))).await; + this.0.strings.borrow_mut().extend(tokens.iter().cloned()); + let i = iv(&tokens).await; + this.0.string_vecs.borrow_mut().insert(i.clone()); + handle.reply(&v, &i.to_api()).await + }, + api::IntReq::ExternStr(si) => { + let i = es(si.0).await; + this.0.strings.borrow_mut().insert(i.clone()); + handle.reply(&si, &i.to_string()).await + }, + api::IntReq::ExternStrv(vi) => { + let i = ev(vi.0).await; + this.0.strings.borrow_mut().extend(i.iter().cloned()); + this.0.string_vecs.borrow_mut().insert(i.clone()); + let markerv = i.iter().map(|t| t.to_api()).collect_vec(); + handle.reply(&vi, &markerv).await + }, + }, + api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => { + let sys = + ctx.system_inst(atom.owner).await.expect("owner of live atom dropped"); + let client = sys.client(); + let reply = + client.request(api::Fwded(fw.0.clone(), *key, body.clone())).await.unwrap(); + handle.reply(fw, &reply).await + }, + api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => { + let sys = ctx.system_inst(id).await.unwrap(); + handle.reply(fw, &sys.request(body.clone()).await).await + }, + api::ExtHostReq::SubLex(sl) => { + let (rep_in, mut rep_out) = channel(0); + { + let lex_g = this.0.lex_recur.lock().await; + let mut req_in = + lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid"); + req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap(); + } + handle.reply(&sl, &rep_out.next().await.unwrap()).await + }, + api::ExtHostReq::ExprReq(expr_req) => match expr_req { + api::ExprReq::Inspect(ins @ api::Inspect { target }) => { + let expr = ctx.exprs.get_expr(target).expect("Invalid ticket"); + handle + .reply(&ins, &api::Inspected { + refcount: expr.strong_count() as u32, + location: expr.pos().to_api(), + kind: expr.to_api().await, + }) + .await + }, + api::ExprReq::Create(ref cre @ api::Create(ref expr)) => { + let expr = Expr::from_api(expr, PathSetBuilder::new(), ctx.clone()).await; + let expr_id = expr.id(); + ctx.exprs.give_expr(expr); + handle.reply(cre, &expr_id).await + }, + }, + api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => { + let reply: ::Response = 'reply: { + let path = ev(path).await; + let root = (ctx.root.read().await.upgrade()) + .expect("LSModule called when root isn't in context"); + let root_data = &*root.0.read().await; + let mut walk_ctx = (ctx.clone(), &root_data.consts); + let module = + match walk(&root_data.root, false, path.iter().cloned(), &mut walk_ctx) + .await + { + Ok(module) => module, + Err(ChildError { kind, .. }) => + break 'reply Err(match kind { + ChildErrorKind::Private => panic!("Access checking was disabled"), + ChildErrorKind::Constant => api::LsModuleError::IsConstant, + ChildErrorKind::Missing => api::LsModuleError::InvalidPath, + }), + }; + let mut members = std::collections::HashMap::new(); + for (k, v) in &module.members { + let kind = match v.kind(ctx.clone(), &root_data.consts).await { + MemberKind::Const => api::MemberInfoKind::Constant, + MemberKind::Module(_) => api::MemberInfoKind::Module, + }; + members.insert(k.to_api(), api::MemberInfo { public: v.public, kind }); + } + Ok(api::ModuleInfo { members }) + }; + handle.reply(ls, &reply).await + }, + api::ExtHostReq::ResolveNames(ref rn) => { + let api::ResolveNames { constid, names, sys } = rn; + let mut resolver = { + let systems = ctx.systems.read().await; + let weak_sys = systems.get(sys).expect("ResolveNames for invalid sys"); + let sys = weak_sys.upgrade().expect("ResolveNames after sys drop"); + sys.name_resolver(*constid).await + }; + let responses = stream(async |mut cx| { + for name in names { + cx.emit(match resolver(&ev(*name).await[..]).await { + Ok(abs) => { + let sym = abs.to_sym().await; + this.0.string_vecs.borrow_mut().insert(sym.tok()); + Ok(sym.to_api()) + }, + Err(e) => { + (this.0.strings.borrow_mut()) + .extend(e.iter().map(|e| e.description.clone())); + Err(e.to_api()) + }, + }) + .await + } + }) + .collect() + .await; + handle.reply(rn, &responses).await + }, + api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => { + let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await; + let unit = atom.print(&FmtCtxImpl::default()).await; + handle.reply(eap, &unit.to_api()).await + }, + } + }) + .await + }, + ) + .await + .unwrap(); }); ExtensionData { - name: header.name.clone(), - ctx: ctx.clone(), + name: header2.name.clone(), + ctx: ctx2, systems: (header.systems.iter().cloned()) - .map(|decl| SystemCtor { decl, ext: WeakExtension(weak.clone()) }) + .map(|decl| SystemCtor { decl, ext: WeakExtension(weak2.clone()) }) .collect(), join_ext: Some(join_ext), next_pars: RefCell::new(NonZeroU64::new(1).unwrap()), @@ -282,7 +295,7 @@ impl Extension { #[must_use] pub async fn is_own_sys(&self, id: api::SysId) -> bool { let Some(sys) = self.ctx().system_inst(id).await else { - writeln!(logger(), "Invalid system ID {id:?}"); + writeln!(log("warn"), "Invalid system ID {id:?}").await; return false; }; Rc::ptr_eq(&self.0, &sys.ext().0) diff --git a/orchid-host/src/lib.rs b/orchid-host/src/lib.rs index 929a57d..33da293 100644 --- a/orchid-host/src/lib.rs +++ b/orchid-host/src/lib.rs @@ -3,11 +3,13 @@ use orchid_api as api; pub mod atom; pub mod ctx; pub mod dealias; +pub mod dylib; pub mod execute; pub mod expr; pub mod expr_store; pub mod extension; pub mod lex; +pub mod logger; pub mod parse; pub mod parsed; pub mod subprocess; diff --git a/orchid-host/src/logger.rs b/orchid-host/src/logger.rs new file mode 100644 index 0000000..0bc4867 --- /dev/null +++ b/orchid-host/src/logger.rs @@ -0,0 +1,84 @@ +use std::fmt::Arguments; +use std::fs::File; +use std::io::{Write, stderr}; +use std::rc::Rc; + +use futures::future::LocalBoxFuture; +use hashbrown::HashMap; +use itertools::Itertools; +use orchid_base::logging::{LogWriter, Logger}; + +use crate::api; + +pub struct LogWriterImpl(api::LogStrategy); +impl LogWriter for LogWriterImpl { + fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> { + Box::pin(async move { + match &self.0 { + api::LogStrategy::Discard => (), + api::LogStrategy::Default => { + stderr().write_fmt(fmt).expect("Could not write to stderr!"); + stderr().flush().expect("Could not flush stderr") + }, + api::LogStrategy::File { path, .. } => { + let mut file = (File::options().write(true).create(true).truncate(false).open(path)) + .unwrap_or_else(|e| panic!("Could not open {path}: {e}")); + file.write_fmt(fmt).unwrap_or_else(|e| panic!("Could not write to {path}: {e}")); + }, + } + }) + } +} + +#[derive(Clone, Default)] +pub struct LoggerImpl { + routing: HashMap, + default: Option, +} +impl LoggerImpl { + pub fn to_api(&self) -> api::Logger { + api::Logger { + default: self.default.clone(), + routing: self.routing.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), + } + } + + pub fn new( + default: Option, + strats: impl IntoIterator, + ) -> Self { + Self { routing: strats.into_iter().collect(), default } + } + pub fn set_default(&mut self, strat: api::LogStrategy) { self.default = Some(strat) } + pub fn clear_default(&mut self) { self.default = None } + pub fn set_category(&mut self, category: &str, strat: api::LogStrategy) { + self.routing.insert(category.to_string(), strat); + } + pub fn with_default(mut self, strat: api::LogStrategy) -> Self { + self.set_default(strat); + self + } + pub fn with_category(mut self, category: &str, strat: api::LogStrategy) -> Self { + self.set_category(category, strat); + self + } + pub async fn log(&self, category: &str, msg: impl AsRef) { + writeln!(self.writer(category), "{}", msg.as_ref()).await + } + pub fn has_category(&self, category: &str) -> bool { self.routing.contains_key(category) } + pub async fn log_buf(&self, category: &str, event: impl AsRef, buf: &[u8]) { + if std::env::var("ORCHID_LOG_BUFFERS").is_ok_and(|v| !v.is_empty()) { + let data = buf.iter().map(|b| format!("{b:02x}")).join(" "); + writeln!(self.writer(category), "{}: [{data}]", event.as_ref()).await + } + } +} +impl Logger for LoggerImpl { + fn writer(&self, category: &str) -> Rc { + Rc::new(LogWriterImpl(self.strat(category).clone())) + } + fn strat(&self, category: &str) -> api::LogStrategy { + (self.routing.get(category).cloned().or(self.default.clone())) + .expect("Invalid category and catchall logger not set") + } +} diff --git a/orchid-host/src/subprocess.rs b/orchid-host/src/subprocess.rs index 298550f..1b915c4 100644 --- a/orchid-host/src/subprocess.rs +++ b/orchid-host/src/subprocess.rs @@ -1,32 +1,34 @@ -use std::io; +use std::{io, process}; -use async_process; use futures::io::BufReader; -use futures::{self, AsyncBufReadExt}; -use orchid_base::logging::logger; +use futures::{self, AsyncBufReadExt, StreamExt}; +use orchid_base::logging::log; +#[cfg(feature = "tokio")] +use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; use crate::ctx::Ctx; use crate::extension::ExtPort; +#[cfg(feature = "tokio")] pub async fn ext_command(cmd: std::process::Command, ctx: Ctx) -> io::Result { - let mut child = async_process::Command::from(cmd) - .stdin(async_process::Stdio::piped()) - .stdout(async_process::Stdio::piped()) - .stderr(async_process::Stdio::piped()) + let name = cmd.get_program().to_string_lossy().to_string(); + let mut child = tokio::process::Command::from(cmd) + .stdin(process::Stdio::piped()) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) .spawn()?; + std::thread::spawn(|| {}); let stdin = child.stdin.take().unwrap(); let stdout = child.stdout.take().unwrap(); - let mut child_stderr = child.stderr.take().unwrap(); + let child_stderr = child.stderr.take().unwrap(); let _ = ctx.spawn(Box::pin(async move { let _ = child; - let mut reader = BufReader::new(&mut child_stderr); - loop { - let mut buf = String::new(); - if 0 == reader.read_line(&mut buf).await.unwrap() { - break; - } - logger().log(buf.strip_suffix('\n').expect("Readline implies this")); + let mut lines = BufReader::new(child_stderr.compat()).lines(); + while let Some(line) = lines.next().await { + // route stderr with an empty category string. This is not the intended logging + // method + writeln!(log("stderr"), "{} err> {}", name, line.expect("Readline implies this")).await; } })); - Ok(ExtPort { input: Box::pin(stdin), output: Box::pin(stdout) }) + Ok(ExtPort { input: Box::pin(stdin.compat_write()), output: Box::pin(stdout.compat()) }) } diff --git a/orchid-host/src/system.rs b/orchid-host/src/system.rs index 2d3fab6..29c28ab 100644 --- a/orchid-host/src/system.rs +++ b/orchid-host/src/system.rs @@ -14,9 +14,10 @@ use orchid_base::error::{OrcRes, mk_errv_floating}; use orchid_base::format::{FmtCtx, FmtUnit, Format}; use orchid_base::interner::{IStr, es, is}; use orchid_base::iter_utils::IteratorPrint; -use orchid_base::logging::logger; +use orchid_base::logging::log; use orchid_base::name::{NameLike, Sym, VName, VPath}; use orchid_base::reqnot::{Client, ClientExt}; +use orchid_base::stash::stash; use ordered_float::NotNan; use substack::{Stackframe, Substack}; @@ -86,7 +87,8 @@ impl System { #[must_use] pub fn can_lex(&self, c: char) -> bool { let ret = char_filter_match(&self.0.lex_filter, c); - writeln!(logger(), "{} can lex {c}: {}", self.ctor().name(), ret); + let ctor = self.ctor(); + stash(async move { writeln!(log("debug"), "{} can lex {c}: {}", ctor.name(), ret).await }); ret } #[must_use] diff --git a/orchid-std/Cargo.toml b/orchid-std/Cargo.toml index dec2e23..c9f31e5 100644 --- a/orchid-std/Cargo.toml +++ b/orchid-std/Cargo.toml @@ -3,11 +3,19 @@ name = "orchid-std" version = "0.1.0" edition = "2024" +[[bin]] +name = "orchid-std" +path = "src/main.rs" + +[lib] +crate-type = ["cdylib", "lib"] +path = "src/lib.rs" + [dependencies] async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" } async-once-cell = "0.5.4" futures = { version = "0.3.31", features = ["std"], default-features = false } -hashbrown = "0.16.0" +hashbrown = "0.16.1" itertools = "0.14.0" never = "0.1.0" once_cell = "1.21.3" @@ -18,12 +26,12 @@ orchid-base = { version = "0.1.0", path = "../orchid-base" } orchid-extension = { version = "0.1.0", path = "../orchid-extension", features = [ "tokio", ] } -ordered-float = "5.0.0" -pastey = "0.1.1" -rust_decimal = "1.38.0" +ordered-float = "5.1.0" +pastey = "0.2.1" +rust_decimal = "1.39.0" subslice-offset = "0.1.1" substack = "1.1.1" -tokio = { version = "1.47.1", features = ["full"] } +tokio = { version = "1.49.0", features = ["full"] } [dev-dependencies] -test_executors = "0.3.5" +test_executors = "0.4.1" diff --git a/orchid-std/src/lib.rs b/orchid-std/src/lib.rs index d1c6fd3..6a840a1 100644 --- a/orchid-std/src/lib.rs +++ b/orchid-std/src/lib.rs @@ -11,3 +11,12 @@ pub use std::tuple::{HomoTpl, Tpl, Tuple, UntypedTuple}; pub use macros::macro_system::MacroSystem; pub use macros::mactree::{MacTok, MacTree}; use orchid_api as api; +use orchid_extension::binary::orchid_extension_main_body; +use orchid_extension::entrypoint::ExtensionBuilder; + +pub extern "C" fn orchid_extension_main(cx: api::binary::ExtensionContext) { + orchid_extension_main_body( + cx, + ExtensionBuilder::new("orchid-std::main").system(StdSystem).system(MacroSystem), + ); +} diff --git a/orchid-std/src/macros/resolve.rs b/orchid-std/src/macros/resolve.rs index cf11b51..07a5d84 100644 --- a/orchid-std/src/macros/resolve.rs +++ b/orchid-std/src/macros/resolve.rs @@ -8,7 +8,7 @@ use orchid_base::error::mk_errv; use orchid_base::format::fmt; use orchid_base::interner::is; use orchid_base::location::Pos; -use orchid_base::logging::logger; +use orchid_base::logging::log; use orchid_base::name::{NameLike, Sym, VPath}; use orchid_base::tree::Paren; use orchid_extension::atom::TAtom; @@ -27,9 +27,7 @@ use crate::{MacTok, MacTree}; pub async fn resolve(val: MacTree) -> GExpr { exec(async move |mut h| { - // if ctx.logger().is_active() { - writeln!(logger(), "Macro-resolving {}", fmt(&val).await); - // } + writeln!(log("debug"), "Macro-resolving {}", fmt(&val).await).await; let root = refl(); let mut macros = HashMap::new(); for n in val.glossary() { @@ -67,7 +65,13 @@ pub async fn resolve(val: MacTree) -> GExpr { } let mut rctx = ResolveCtx { h, exclusive, priod }; let gex = resolve_one(&mut rctx, Substack::Bottom, &val).await; - writeln!(logger(), "Macro-resolution over {}\nreturned {}", fmt(&val).await, fmt(&gex).await); + writeln!( + log("debug"), + "Macro-resolution over {}\nreturned {}", + fmt(&val).await, + fmt(&gex).await + ) + .await; gex }) .await diff --git a/orcx/Cargo.toml b/orcx/Cargo.toml index ba10cc5..a09deda 100644 --- a/orcx/Cargo.toml +++ b/orcx/Cargo.toml @@ -7,13 +7,15 @@ edition = "2024" [dependencies] async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" } -camino = "1.1.9" -clap = { version = "4.5.24", features = ["derive", "env"] } -ctrlc = "3.4.5" +camino = "1.2.2" +clap = { version = "4.5.54", features = ["derive", "env"] } +ctrlc = "3.5.1" futures = "0.3.31" itertools = "0.14.0" orchid-api = { version = "0.1.0", path = "../orchid-api" } orchid-base = { version = "0.1.0", path = "../orchid-base" } -orchid-host = { version = "0.1.0", path = "../orchid-host" } +orchid-host = { version = "0.1.0", path = "../orchid-host", features = [ + "tokio", +] } substack = "1.1.1" -tokio = { version = "1.43.0", features = ["full"] } +tokio = { version = "1.49.0", features = ["full"] } diff --git a/orcx/src/main.rs b/orcx/src/main.rs index 8172612..d5973d2 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -1,3 +1,5 @@ +use orchid_base::logging::Logger; +use tokio::time::Instant; pub mod parse_folder; use std::cell::RefCell; @@ -17,9 +19,10 @@ use orchid_base::format::{FmtCtxImpl, Format, fmt, fmt_v, take_first}; use orchid_base::interner::local_interner::local_interner; use orchid_base::interner::{is, with_interner}; use orchid_base::location::SrcRange; -use orchid_base::logging::{LogStrategy, Logger, with_logger}; +use orchid_base::logging::{log, with_logger}; use orchid_base::name::{NameLike, VPath}; use orchid_base::parse::{Import, Snippet}; +use orchid_base::stash::with_stash; use orchid_base::sym; use orchid_base::tree::{Token, ttv_fmt}; use orchid_host::ctx::{Ctx, JoinHandle, Spawner}; @@ -27,6 +30,7 @@ use orchid_host::execute::{ExecCtx, ExecResult}; use orchid_host::expr::ExprKind; use orchid_host::extension::Extension; use orchid_host::lex::lex; +use orchid_host::logger::LoggerImpl; use orchid_host::parse::{HostParseCtxImpl, parse_item, parse_items}; use orchid_host::parsed::{Item, ItemKind, ParsTokTree, ParsedMember, ParsedModule}; use orchid_host::subprocess::ext_command; @@ -45,12 +49,12 @@ pub struct Args { extension: Vec, #[arg(short, long, env = "ORCHID_DEFAULT_SYSTEMS", value_delimiter = ';')] system: Vec, - #[arg(short, long)] - logs: bool, - #[arg(short, long)] - msg_logs: bool, + #[arg(short, long, default_value = "off", default_missing_value = "stderr")] + logs: Vec, #[command(subcommand)] command: Commands, + #[arg(long, action)] + time: bool, } #[derive(Subcommand, Debug)] @@ -78,6 +82,20 @@ pub enum Commands { }, } +static mut STARTUP: Option = None; +fn time_print(args: &Args, msg: &str) { + if !args.time { + return; + } + let ms = unsafe { STARTUP }.unwrap().elapsed().as_millis(); + let secs = ms / 1000; + let mins = secs / 60; + if mins == 0 { + eprintln!("{secs}.{ms:>40} {msg}"); + } + eprintln!("{mins}:{secs:>2}.{ms:>40} {msg}") +} + fn get_all_extensions<'a>( args: &'a Args, ctx: &'a Ctx, @@ -92,6 +110,38 @@ fn get_all_extensions<'a>( }) } +fn parse_log_dest(dest: &str) -> orchid_api::LogStrategy { + if dest == "off" { + orchid_api::LogStrategy::Discard + } else if dest == "stderr" { + orchid_api::LogStrategy::Default + } else if let Some(path) = dest.strip_prefix('>') { + orchid_api::LogStrategy::File { path: path.to_string(), append: true } + } else { + orchid_api::LogStrategy::File { path: dest.to_string(), append: false } + } +} + +fn get_logger(args: &Args) -> LoggerImpl { + let mut logger = LoggerImpl::default(); + for cmd in &args.logs { + match cmd.split_once(">") { + None => logger.set_default(parse_log_dest(cmd)), + Some((category, dest)) => logger.set_category(category, parse_log_dest(dest)), + } + } + if !logger.has_category("warn") { + logger.set_category("warn", logger.strat("debug")); + } + if !logger.has_category("error") { + logger.set_category("error", logger.strat("warn")); + } + if !logger.has_category("msg") { + logger.set_category("msg", orchid_api::LogStrategy::Discard); + } + logger +} + struct JoinHandleImpl(tokio::task::JoinHandle<()>); impl JoinHandle for JoinHandleImpl { fn abort(&self) { self.0.abort() } @@ -109,258 +159,270 @@ impl Spawner for SpawnerImpl { #[tokio::main] async fn main() -> io::Result { - eprintln!("orcx launched"); + eprintln!("Orcx was made by Lawrence Bethlenfalvy"); + let args = Args::parse(); let exit_code = Rc::new(RefCell::new(ExitCode::SUCCESS)); let local_set = LocalSet::new(); let exit_code1 = exit_code.clone(); - let args = Args::parse(); - let logger = Logger::new(if args.logs { LogStrategy::StdErr } else { LogStrategy::Discard }); - let cx_logger = logger.clone(); - let msg_logger = - Logger::new(if args.msg_logs { LogStrategy::StdErr } else { LogStrategy::Discard }); + let logger = get_logger(&args); + let logger2 = logger.clone(); + unsafe { STARTUP = Some(Instant::now()) }; local_set.spawn_local(async move { - let ctx = &Ctx::new(msg_logger.clone(), SpawnerImpl); - let extensions = get_all_extensions(&args, ctx).try_collect::>().await.unwrap(); - match args.command { - Commands::Lex { file } => { - let (_, systems) = init_systems(&args.system, &extensions).await.unwrap(); - let mut file = File::open(file.as_std_path()).unwrap(); - let mut buf = String::new(); - file.read_to_string(&mut buf).unwrap(); - let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap(); - println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true)) - }, - Commands::Parse { file } => { - let (_, systems) = init_systems(&args.system, &extensions).await.unwrap(); - let mut file = File::open(file.as_std_path()).unwrap(); - let mut buf = String::new(); - file.read_to_string(&mut buf).unwrap(); - let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap(); - let Some(first) = lexemes.first() else { - println!("File empty!"); - return; - }; - let pctx = HostParseCtxImpl { systems: &systems, ctx: ctx.clone(), src: sym!(usercode) }; - let snip = Snippet::new(first, &lexemes); - match with_reporter(parse_items(&pctx, Substack::Bottom, snip)).await.unwrap() { - Err(errv) => { - eprintln!("{errv}"); - *exit_code1.borrow_mut() = ExitCode::FAILURE; - }, - Ok(ptree) if ptree.is_empty() => { - eprintln!("File empty only after parsing, but no errors were reported"); - *exit_code1.borrow_mut() = ExitCode::FAILURE; - }, - Ok(ptree) => - for item in ptree { - println!("{}", take_first(&item.print(&FmtCtxImpl::default()).await, true)) - }, - }; - }, - Commands::Repl => { - let mut counter = 0; - let mut imports = Vec::new(); - let usercode_path = sym!(usercode); - let mut stdin = BufReader::new(stdin()); - loop { - counter += 1; - let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap(); - print!("\\.> "); - std::io::stdout().flush().unwrap(); - let mut prompt = String::new(); - stdin.read_line(&mut prompt).await.unwrap(); - let name = is(&format!("_{counter}")).await; - let path = usercode_path.suffix([name.clone()]).await; - let mut lexemes = - lex(is(prompt.trim()).await, path.clone(), &systems, ctx).await.unwrap(); - let Some(discr) = lexemes.first() else { continue }; - if args.logs { - println!( - "lexed: {}", - take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true) - ); - } - let prefix_sr = SrcRange::zw(path.clone(), 0); - let process_lexemes = async |lexemes: &[ParsTokTree]| { - let snippet = Snippet::new(&lexemes[0], lexemes); - let parse_ctx = - HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] }; - match try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet)).await - { - Ok(items) => Some(items), - Err(e) => { - eprintln!("{e}"); - None - }, - } + let ctx = &Ctx::new(SpawnerImpl, logger2); + with_stash(async { + let extensions = + get_all_extensions(&args, ctx).try_collect::>().await.unwrap(); + time_print(&args, "Extensions loaded"); + match args.command { + Commands::Lex { file } => { + let (_, systems) = init_systems(&args.system, &extensions).await.unwrap(); + let mut file = File::open(file.as_std_path()).unwrap(); + let mut buf = String::new(); + file.read_to_string(&mut buf).unwrap(); + let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap(); + println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true)) + }, + Commands::Parse { file } => { + let (_, systems) = init_systems(&args.system, &extensions).await.unwrap(); + let mut file = File::open(file.as_std_path()).unwrap(); + let mut buf = String::new(); + file.read_to_string(&mut buf).unwrap(); + let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap(); + let Some(first) = lexemes.first() else { + println!("File empty!"); + return; }; - let add_imports = |items: &mut Vec, imports: &[Import]| { - items.extend(imports.iter().map(|import| Item::new(import.sr.clone(), import.clone()))); - }; - if discr.is_kw(is("import").await) { - let Some(import_lines) = process_lexemes(&lexemes).await else { continue }; - imports.extend(import_lines.into_iter().map(|it| match it.kind { - ItemKind::Import(imp) => imp, - _ => panic!("Expected imports from import line"), - })); - continue; - } - if !discr.is_kw(is("let").await) { - let prefix = [is("export").await, is("let").await, name.clone(), is("=").await]; - lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone()))); - } - let Some(mut new_lines) = process_lexemes(&lexemes).await else { continue }; - let const_decl = new_lines.iter().exactly_one().expect("Multiple lines from let"); - let input_sr = const_decl.sr.map_range(|_| 0..0); - let const_name = match &const_decl.kind { - ItemKind::Member(ParsedMember { name: const_name, .. }) => const_name.clone(), - _ => panic!("Expected exactly one constant declaration from let"), - }; - add_imports(&mut new_lines, &imports); - imports.push(Import::new(input_sr.clone(), VPath::new(path.segs()), const_name.clone())); - let new_module = ParsedModule::new(true, new_lines); - match with_reporter(root.add_parsed(&new_module, path.clone())).await { - Ok(new) => root = new, + let pctx = HostParseCtxImpl { systems: &systems, ctx: ctx.clone(), src: sym!(usercode) }; + let snip = Snippet::new(first, &lexemes); + match with_reporter(parse_items(&pctx, Substack::Bottom, snip)).await.unwrap() { Err(errv) => { eprintln!("{errv}"); *exit_code1.borrow_mut() = ExitCode::FAILURE; + }, + Ok(ptree) if ptree.is_empty() => { + eprintln!("File empty only after parsing, but no errors were reported"); + *exit_code1.borrow_mut() = ExitCode::FAILURE; + }, + Ok(ptree) => + for item in ptree { + println!("{}", take_first(&item.print(&FmtCtxImpl::default()).await, true)) + }, + }; + }, + Commands::Repl => { + let mut counter = 0; + let mut imports = Vec::new(); + let usercode_path = sym!(usercode); + let mut stdin = BufReader::new(stdin()); + loop { + counter += 1; + let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap(); + print!("\\.> "); + std::io::stdout().flush().unwrap(); + let mut prompt = String::new(); + stdin.read_line(&mut prompt).await.unwrap(); + let name = is(&format!("_{counter}")).await; + let path = usercode_path.suffix([name.clone()]).await; + let mut lexemes = + lex(is(prompt.trim()).await, path.clone(), &systems, ctx).await.unwrap(); + let Some(discr) = lexemes.first() else { continue }; + writeln!( + log("debug"), + "lexed: {}", + take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true) + ) + .await; + let prefix_sr = SrcRange::zw(path.clone(), 0); + let process_lexemes = async |lexemes: &[ParsTokTree]| { + let snippet = Snippet::new(&lexemes[0], lexemes); + let parse_ctx = + HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] }; + match try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet)) + .await + { + Ok(items) => Some(items), + Err(e) => { + eprintln!("{e}"); + None + }, + } + }; + let add_imports = |items: &mut Vec, imports: &[Import]| { + items + .extend(imports.iter().map(|import| Item::new(import.sr.clone(), import.clone()))); + }; + if discr.is_kw(is("import").await) { + let Some(import_lines) = process_lexemes(&lexemes).await else { continue }; + imports.extend(import_lines.into_iter().map(|it| match it.kind { + ItemKind::Import(imp) => imp, + _ => panic!("Expected imports from import line"), + })); + continue; + } + if !discr.is_kw(is("let").await) { + let prefix = [is("export").await, is("let").await, name.clone(), is("=").await]; + lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone()))); + } + let Some(mut new_lines) = process_lexemes(&lexemes).await else { continue }; + let const_decl = new_lines.iter().exactly_one().expect("Multiple lines from let"); + let input_sr = const_decl.sr.map_range(|_| 0..0); + let const_name = match &const_decl.kind { + ItemKind::Member(ParsedMember { name: const_name, .. }) => const_name.clone(), + _ => panic!("Expected exactly one constant declaration from let"), + }; + add_imports(&mut new_lines, &imports); + imports.push(Import::new( + input_sr.clone(), + VPath::new(path.segs()), + const_name.clone(), + )); + let new_module = ParsedModule::new(true, new_lines); + match with_reporter(root.add_parsed(&new_module, path.clone())).await { + Ok(new) => root = new, + Err(errv) => { + eprintln!("{errv}"); + *exit_code1.borrow_mut() = ExitCode::FAILURE; + return; + }, + } + eprintln!("parsed"); + let entrypoint = + ExprKind::Const(path.suffix([const_name.clone()]).await).at(input_sr.pos()); + let mut xctx = ExecCtx::new(root.clone(), entrypoint).await; + eprintln!("executed"); + xctx.set_gas(Some(1000)); + xctx.execute().await; + match xctx.result() { + ExecResult::Value(val) => println!( + "{const_name} = {}", + take_first(&val.print(&FmtCtxImpl::default()).await, false) + ), + ExecResult::Err(e) => println!("error: {e}"), + ExecResult::Gas(_) => println!("Ran out of gas!"), + } + } + }, + Commands::ModTree { proj, prefix } => { + let (mut root, _systems) = init_systems(&args.system, &extensions).await.unwrap(); + if let Some(proj_path) = proj { + let path = proj_path.into_std_path_buf(); + match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await { + Ok(r) => root = r, + Err(e) => { + eprintln!("{e}"); + *exit_code1.borrow_mut() = ExitCode::FAILURE; + return; + }, + } + } + let prefix = match prefix { + Some(pref) => VPath::parse(&pref).await, + None => VPath::new([]), + }; + let root_data = root.0.read().await; + print_mod(&root_data.root, prefix, &root_data).await; + async fn print_mod(module: &Module, path: VPath, root: &RootData) { + let indent = " ".repeat(path.len()); + for (key, tgt) in &module.imports { + match tgt { + Ok(tgt) => println!("{indent}import {key} => {}", tgt.target), + Err(opts) => println!( + "{indent}import {key} conflicts between {}", + opts.iter().map(|i| &i.target).join(" ") + ), + } + } + for (key, mem) in &module.members { + let new_path = path.clone().name_with_suffix(key.clone()).to_sym().await; + match mem.kind(root.ctx.clone(), &root.consts).await { + MemberKind::Module(module) => { + println!("{indent}module {key} {{"); + print_mod(module, VPath::new(new_path.segs()), root).boxed_local().await; + println!("{indent}}}") + }, + MemberKind::Const => { + let value = root.consts.get(&new_path).expect("Missing const!"); + println!("{indent}const {key} = {}", fmt(value).await) + }, + } + } + } + }, + Commands::Exec { proj, code } => { + let path = sym!(usercode); + let prefix_sr = SrcRange::zw(path.clone(), 0); + let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap(); + if let Some(proj_path) = proj { + let path = proj_path.into_std_path_buf(); + match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await { + Ok(r) => root = r, + Err(e) => { + eprintln!("{e}"); + *exit_code1.borrow_mut() = ExitCode::FAILURE; + return; + }, + } + } + let mut lexemes = match lex(is(code.trim()).await, path.clone(), &systems, ctx).await { + Ok(lexemes) => { + writeln!( + log("debug"), + "lexed: {}", + fmt_v::(lexemes.iter()).await.join(" ") + ) + .await; + lexemes + }, + Err(e) => { + eprintln!("{e}"); + *exit_code1.borrow_mut() = ExitCode::FAILURE; return; }, - } - eprintln!("parsed"); - let entrypoint = - ExprKind::Const(path.suffix([const_name.clone()]).await).at(input_sr.pos()); - let mut xctx = ExecCtx::new(root.clone(), entrypoint).await; - eprintln!("executed"); - xctx.set_gas(Some(1000)); + }; + let parse_ctx = + HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] }; + let prefix = [is("export").await, is("let").await, is("entrypoint").await, is("=").await]; + lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone()))); + let snippet = Snippet::new(&lexemes[0], &lexemes); + let entrypoint = match try_with_reporter(parse_item( + &parse_ctx, + Substack::Bottom, + vec![], + snippet, + )) + .await + { + Ok(items) => ParsedModule::new(true, items), + Err(e) => { + eprintln!("{e}"); + *exit_code1.borrow_mut() = ExitCode::FAILURE; + return; + }, + }; + let root = match with_reporter(root.add_parsed(&entrypoint, path.clone())).await { + Err(e) => { + eprintln!("{e}"); + *exit_code1.borrow_mut() = ExitCode::FAILURE; + return; + }, + Ok(new_root) => new_root, + }; + let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos()); + let mut xctx = ExecCtx::new(root, expr).await; + xctx.set_gas(Some(10_000)); xctx.execute().await; match xctx.result() { - ExecResult::Value(val) => println!( - "{const_name} = {}", - take_first(&val.print(&FmtCtxImpl::default()).await, false) - ), + ExecResult::Value(val) => + println!("{}", take_first(&val.print(&FmtCtxImpl::default()).await, false)), ExecResult::Err(e) => println!("error: {e}"), ExecResult::Gas(_) => println!("Ran out of gas!"), } - } - }, - Commands::ModTree { proj, prefix } => { - let (mut root, _systems) = init_systems(&args.system, &extensions).await.unwrap(); - if let Some(proj_path) = proj { - let path = proj_path.into_std_path_buf(); - match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await { - Ok(r) => root = r, - Err(e) => { - eprintln!("{e}"); - *exit_code1.borrow_mut() = ExitCode::FAILURE; - return; - }, - } - } - let prefix = match prefix { - Some(pref) => VPath::parse(&pref).await, - None => VPath::new([]), - }; - let root_data = root.0.read().await; - print_mod(&root_data.root, prefix, &root_data).await; - async fn print_mod(module: &Module, path: VPath, root: &RootData) { - let indent = " ".repeat(path.len()); - for (key, tgt) in &module.imports { - match tgt { - Ok(tgt) => println!("{indent}import {key} => {}", tgt.target), - Err(opts) => println!( - "{indent}import {key} conflicts between {}", - opts.iter().map(|i| &i.target).join(" ") - ), - } - } - for (key, mem) in &module.members { - let new_path = path.clone().name_with_suffix(key.clone()).to_sym().await; - match mem.kind(root.ctx.clone(), &root.consts).await { - MemberKind::Module(module) => { - println!("{indent}module {key} {{"); - print_mod(module, VPath::new(new_path.segs()), root).boxed_local().await; - println!("{indent}}}") - }, - MemberKind::Const => { - let value = root.consts.get(&new_path).expect("Missing const!"); - println!("{indent}const {key} = {}", fmt(value).await) - }, - } - } - } - }, - Commands::Exec { proj, code } => { - eprintln!("exec branch"); - let path = sym!(usercode); - let prefix_sr = SrcRange::zw(path.clone(), 0); - let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap(); - if let Some(proj_path) = proj { - let path = proj_path.into_std_path_buf(); - match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await { - Ok(r) => root = r, - Err(e) => { - eprintln!("{e}"); - *exit_code1.borrow_mut() = ExitCode::FAILURE; - return; - }, - } - } - let mut lexemes = match lex(is(code.trim()).await, path.clone(), &systems, ctx).await { - Ok(lexemes) => { - if args.logs { - println!("lexed: {}", fmt_v::(lexemes.iter()).await.join(" ")); - } - lexemes - }, - Err(e) => { - eprintln!("{e}"); - *exit_code1.borrow_mut() = ExitCode::FAILURE; - return; - }, - }; - let parse_ctx = - HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] }; - let prefix = [is("export").await, is("let").await, is("entrypoint").await, is("=").await]; - lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone()))); - let snippet = Snippet::new(&lexemes[0], &lexemes); - let entrypoint = match try_with_reporter(parse_item( - &parse_ctx, - Substack::Bottom, - vec![], - snippet, - )) - .await - { - Ok(items) => ParsedModule::new(true, items), - Err(e) => { - eprintln!("{e}"); - *exit_code1.borrow_mut() = ExitCode::FAILURE; - return; - }, - }; - let root = match with_reporter(root.add_parsed(&entrypoint, path.clone())).await { - Err(e) => { - eprintln!("{e}"); - *exit_code1.borrow_mut() = ExitCode::FAILURE; - return; - }, - Ok(new_root) => new_root, - }; - let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos()); - let mut xctx = ExecCtx::new(root, expr).await; - xctx.set_gas(Some(10_000)); - xctx.execute().await; - match xctx.result() { - ExecResult::Value(val) => - println!("{}", take_first(&val.print(&FmtCtxImpl::default()).await, false)), - ExecResult::Err(e) => println!("error: {e}"), - ExecResult::Gas(_) => println!("Ran out of gas!"), - } - }, - } + }, + } + }) + .await; }); - with_interner(local_interner(), with_logger(cx_logger, local_set)).await; + with_interner(local_interner(), with_logger(logger, local_set)).await; let x = *exit_code.borrow(); Ok(x) } diff --git a/unsync-pipe/Cargo.toml b/unsync-pipe/Cargo.toml index a1a2856..50fe493 100644 --- a/unsync-pipe/Cargo.toml +++ b/unsync-pipe/Cargo.toml @@ -12,7 +12,7 @@ futures = "0.3.31" itertools = "0.14.0" rand = "0.9.2" rand_chacha = "0.9.0" -test_executors = "0.4.0" +test_executors = "0.4.1" [dependencies] futures-io = "0.3.31" diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index f3d9514..38ee12d 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] -clap = { version = "4.5.24", features = ["derive"] } +clap = { version = "4.5.54", features = ["derive"] }