From 9b4c7fa7d7379d4426af1f0d7b1120da5f8752e8 Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Wed, 8 Apr 2026 18:02:20 +0200 Subject: [PATCH] partway through fixes, macro system needs resdesign --- .cargo/config.toml | 9 + Cargo.lock | 48 +- orchid-api-derive/.cargo/config.toml | 2 + orchid-api-traits/src/coding.rs | 10 + orchid-api/src/atom.rs | 16 +- orchid-api/src/binary.rs | 1 - orchid-base/src/comm.rs | 4 +- orchid-base/src/error.rs | 2 +- orchid-base/src/tree.rs | 7 + orchid-extension/Cargo.toml | 1 + orchid-extension/src/atom.rs | 124 ++---- orchid-extension/src/atom_owned.rs | 33 +- orchid-extension/src/atom_thin.rs | 12 +- orchid-extension/src/cmd_atom.rs | 41 +- orchid-extension/src/conv.rs | 8 + orchid-extension/src/coroutine_exec.rs | 12 +- orchid-extension/src/entrypoint.rs | 23 +- orchid-extension/src/expr.rs | 6 +- orchid-extension/src/gen_expr.rs | 139 ++++-- orchid-extension/src/std_reqs.rs | 10 +- orchid-extension/src/tree.rs | 6 +- orchid-host/Cargo.toml | 1 + orchid-host/src/atom.rs | 2 +- orchid-host/src/cmd_system.rs | 164 +++++-- orchid-host/src/ctx.rs | 9 +- orchid-host/src/extension.rs | 18 +- orchid-host/src/lex.rs | 5 +- orchid-host/src/lib.rs | 2 +- orchid-host/src/parse.rs | 4 +- orchid-host/src/parsed.rs | 18 +- orchid-host/src/sys_parser.rs | 6 +- orchid-host/src/tree.rs | 76 ++-- orchid-std/src/macros/instantiate_tpl.rs | 3 +- orchid-std/src/macros/let_line.rs | 13 +- orchid-std/src/macros/macro_lib.rs | 74 ++-- orchid-std/src/macros/macro_line.rs | 4 +- orchid-std/src/macros/macro_system.rs | 2 + orchid-std/src/macros/macro_value.rs | 6 +- orchid-std/src/macros/mactree.rs | 9 +- orchid-std/src/macros/mactree_lexer.rs | 6 +- orchid-std/src/macros/match_macros.rs | 105 ++--- orchid-std/src/macros/mod.rs | 1 + orchid-std/src/macros/ph_lexer.rs | 4 +- orchid-std/src/macros/postmac.rs | 53 +++ orchid-std/src/macros/resolve.rs | 257 +++++++---- orchid-std/src/macros/rule/matcher.rs | 3 +- orchid-std/src/macros/rule/shared.rs | 3 +- orchid-std/src/macros/rule/state.rs | 4 +- orchid-std/src/macros/stdlib/funnctional.rs | 7 +- orchid-std/src/macros/stdlib/option.rs | 20 +- orchid-std/src/macros/stdlib/record.rs | 42 +- orchid-std/src/macros/stdlib/tuple.rs | 85 ++-- orchid-std/src/macros/utils.rs | 85 ++-- orchid-std/src/std/binary/binary_atom.rs | 3 +- orchid-std/src/std/boolean.rs | 4 +- orchid-std/src/std/ops/subscript_lexer.rs | 5 +- orchid-std/src/std/option.rs | 7 +- orchid-std/src/std/protocol/parse_impls.rs | 10 +- orchid-std/src/std/protocol/proto_parser.rs | 3 +- orchid-std/src/std/protocol/type_parser.rs | 3 +- orchid-std/src/std/record/record_atom.rs | 4 +- orchid-std/src/std/record/record_lib.rs | 7 +- orchid-std/src/std/std_system.rs | 4 +- orchid-std/src/std/stream/stream_cmds.rs | 4 +- orchid-std/src/std/string/str_atom.rs | 5 +- orchid-std/src/std/string/str_lexer.rs | 4 +- orchid-std/src/std/time.rs | 29 +- orchid.code-workspace | 66 +++ orcx/Cargo.toml | 1 + orcx/src/main.rs | 460 +++++++------------- orcx/src/parse_folder.rs | 5 +- orcx/src/print_mod.rs | 31 ++ orcx/src/repl.rs | 102 +++++ rustfmt.toml | 1 + unsync-pipe/Cargo.toml | 1 + unsync-pipe/src/lib.rs | 92 ++-- 76 files changed, 1391 insertions(+), 1065 deletions(-) create mode 100644 orchid-api-derive/.cargo/config.toml create mode 100644 orchid-std/src/macros/postmac.rs create mode 100644 orcx/src/print_mod.rs create mode 100644 orcx/src/repl.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 8c33870..e5a15a5 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -13,3 +13,12 @@ RUST_BACKTRACE = "1" [build] # rustflags = ["-Znext-solver"] + +[profile.dev] +opt-level = 0 +debug = 2 +strip = 'none' +debug-assertions = true +overflow-checks = true +lto = false +panic = 'abort' diff --git a/Cargo.lock b/Cargo.lock index 685868f..c17f67f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -297,9 +306,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -994,6 +1003,15 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1087,6 +1105,7 @@ dependencies = [ "async-event", "async-fn-stream", "async-once-cell", + "chrono", "derive_destructure", "dyn-clone", "futures", @@ -1122,6 +1141,7 @@ dependencies = [ "async-fn-stream", "async-once-cell", "bound", + "chrono", "derive_destructure", "futures", "futures-locks", @@ -1187,6 +1207,7 @@ dependencies = [ "orchid-api", "orchid-base", "orchid-host", + "stacker", "substack", "tokio", ] @@ -1293,6 +1314,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" +dependencies = [ + "ar_archive_writer", + "cc", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -1731,6 +1762,19 @@ dependencies = [ "web-time", ] +[[package]] +name = "stacker" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + [[package]] name = "stdio-perftest" version = "0.1.0" diff --git a/orchid-api-derive/.cargo/config.toml b/orchid-api-derive/.cargo/config.toml new file mode 100644 index 0000000..c61fac5 --- /dev/null +++ b/orchid-api-derive/.cargo/config.toml @@ -0,0 +1,2 @@ +[profile.dev] +panic = 'unwind' diff --git a/orchid-api-traits/src/coding.rs b/orchid-api-traits/src/coding.rs index b1599d1..93c82c1 100644 --- a/orchid-api-traits/src/coding.rs +++ b/orchid-api-traits/src/coding.rs @@ -376,3 +376,13 @@ impl Encode for chrono::TimeDelta { self.subsec_nanos().encode(write).await } } +impl Decode for chrono::DateTime { + async fn decode(read: Pin<&mut R>) -> io::Result { + Ok(Self::from_timestamp_micros(i64::decode(read).await?).unwrap()) + } +} +impl Encode for chrono::DateTime { + async fn encode(&self, write: Pin<&mut W>) -> io::Result<()> { + self.timestamp_micros().encode(write).await + } +} diff --git a/orchid-api/src/atom.rs b/orchid-api/src/atom.rs index 260ba2f..231636c 100644 --- a/orchid-api/src/atom.rs +++ b/orchid-api/src/atom.rs @@ -95,14 +95,18 @@ impl Request for DeserAtom { /// A request blindly routed to the system that provides an atom. #[derive(Clone, Debug, Coding, Hierarchy)] #[extends(AtomReq, HostExtReq)] -pub struct FinalFwded(pub Atom, pub TStrv, pub Vec); -impl Request for FinalFwded { +pub struct Fwded(pub Atom, pub TStrv, pub Vec); +impl Request for Fwded { type Response = Option>; } #[derive(Clone, Debug, Coding, Hierarchy)] #[extends(ExtHostReq)] -pub struct Fwd(pub Atom, pub TStrv, pub Vec); +pub struct Fwd { + pub target: Atom, + pub method: TStrv, + pub body: Vec, +} impl Request for Fwd { type Response = Option>; } @@ -138,8 +142,7 @@ impl Request for ExtAtomPrint { pub enum AtomReq { CallRef(CallRef), FinalCall(FinalCall), - FwdedRef(FinalFwded), - FinalFwded(FinalFwded), + Fwded(Fwded), AtomPrint(AtomPrint), SerializeAtom(SerializeAtom), } @@ -150,8 +153,7 @@ impl AtomReq { match self { Self::CallRef(CallRef(a, ..)) | Self::FinalCall(FinalCall(a, ..)) - | Self::FwdedRef(FinalFwded(a, ..)) - | Self::FinalFwded(FinalFwded(a, ..)) + | Self::Fwded(Fwded(a, ..)) | Self::AtomPrint(AtomPrint(a)) | Self::SerializeAtom(SerializeAtom(a)) => a, } diff --git a/orchid-api/src/binary.rs b/orchid-api/src/binary.rs index 75c0292..6ac2070 100644 --- a/orchid-api/src/binary.rs +++ b/orchid-api/src/binary.rs @@ -6,7 +6,6 @@ //! 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 diff --git a/orchid-base/src/comm.rs b/orchid-base/src/comm.rs index 138a3a1..2b89a51 100644 --- a/orchid-base/src/comm.rs +++ b/orchid-base/src/comm.rs @@ -312,9 +312,9 @@ impl<'a> MsgWriter<'a> for IoNotifWriter { fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.o.as_mut() } fn finish(mut self: Box) -> LocalBoxFuture<'static, io::Result<()>> { Box::pin(async move { - self.o.flush().await?; + let ret = self.o.flush().await; self.drop_g.defuse(); - Ok(()) + ret }) } } diff --git a/orchid-base/src/error.rs b/orchid-base/src/error.rs index e06555f..9bd2d55 100644 --- a/orchid-base/src/error.rs +++ b/orchid-base/src/error.rs @@ -142,7 +142,7 @@ impl OrcErrv { /// If there is exactly one error, return it. Mostly used for simplified /// printing #[must_use] - pub fn one(&self) -> Option<&OrcErr> { (self.0.len() == 1).then(|| &self.0[9]) } + pub fn one(&self) -> Option<&OrcErr> { self.0.iter().exactly_one().ok() } /// Iterate over all positions of all errors pub fn pos_iter(&self) -> impl Iterator + '_ { self.0.iter().flat_map(|e| e.positions.iter().cloned()) diff --git a/orchid-base/src/tree.rs b/orchid-base/src/tree.rs index bd1951e..604d0c6 100644 --- a/orchid-base/src/tree.rs +++ b/orchid-base/src/tree.rs @@ -332,5 +332,12 @@ pub async fn ttv_fmt<'a: 'b, 'b>( FmtUnit::sequence("", " ", "", true, join_all(ttv.into_iter().map(|t| t.print(c))).await) } +pub struct FmtTTV<'a, H: ExprRepr, X: ExtraTok>(pub &'a [TokTree]); +impl<'b, H: ExprRepr, X: ExtraTok> Format for FmtTTV<'b, H, X> { + async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + ttv_fmt(self.0, c).await + } +} + /// Indent a string by two spaces pub fn indent(s: &str) -> String { s.replace("\n", "\n ") } diff --git a/orchid-extension/Cargo.toml b/orchid-extension/Cargo.toml index d239e81..1d3766d 100644 --- a/orchid-extension/Cargo.toml +++ b/orchid-extension/Cargo.toml @@ -9,6 +9,7 @@ edition = "2024" async-event = "0.2.1" async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" } async-once-cell = "0.5.4" +chrono = "0.4.44" derive_destructure = "1.0.0" dyn-clone = "1.0.20" futures = { version = "0.3.31", default-features = false, features = [ diff --git a/orchid-extension/src/atom.rs b/orchid-extension/src/atom.rs index 250f183..8f83d73 100644 --- a/orchid-extension/src/atom.rs +++ b/orchid-extension/src/atom.rs @@ -1,5 +1,4 @@ use std::any::{Any, TypeId, type_name}; -use std::cell::RefCell; use std::collections::HashMap; use std::fmt::{self, Debug}; use std::future::Future; @@ -19,7 +18,6 @@ use orchid_base::{ FmtCtx, FmtUnit, Format, IStr, OrcErrv, Pos, Receipt, ReqHandle, ReqReader, ReqReaderExt, Sym, fmt, is, mk_errv, mk_errv_floating, take_first, }; -use task_local::task_local; use trait_set::trait_set; use crate::gen_expr::GExpr; @@ -99,11 +97,11 @@ impl ForeignAtom { /// Call an IPC method. If the type does not support the given method type, /// this function returns [None] pub async fn call>(&self, r: R) -> Option { - let rep = (request(api::Fwd( - self.atom.clone(), - Sym::parse(::Root::NAME).await.unwrap().tok().to_api(), - enc_vec(&r.into_root()), - ))) + let rep = (request(api::Fwd { + target: self.atom.clone(), + method: Sym::parse(::Root::NAME).await.unwrap().tok().to_api(), + body: enc_vec(&r.into_root()), + })) .await?; Some(R::Response::decode_slice(&mut &rep[..])) } @@ -111,26 +109,22 @@ impl ForeignAtom { pub fn downcast(self) -> Result, NotTypAtom> { let mut data = &self.atom.data.0[..]; let value = AtomTypeId::decode_slice(&mut data); - if cfg!(debug_assertions) { - let cted = dyn_cted(); - let own_inst = cted.inst(); - let owner_id = self.atom.owner; - let typ = type_name::(); - let owner = if sys_id() == owner_id { - own_inst.card() - } else { - (cted.deps().find(|s| s.id() == self.atom.owner)) - .ok_or_else(|| NotTypAtom { expr: self.clone().ex(), pos: self.pos(), typ })? - .get_card() - }; - let Some(ops) = owner.ops_by_atid(value) else { - panic!("{value:?} does not refer to an atom in {owner_id:?} when downcasting {typ}"); - }; - if ops.tid() != TypeId::of::() { - panic!( - "{value:?} of {owner_id:?} refers to a type other than {typ}. System version mismatch?" - ) - } + let cted = dyn_cted(); + let own_inst = cted.inst(); + let owner_id = self.atom.owner; + let typ = type_name::(); + let owner = if sys_id() == owner_id { + own_inst.card() + } else { + (cted.deps().find(|s| s.id() == self.atom.owner)) + .ok_or_else(|| NotTypAtom { expr: self.clone().ex(), pos: self.pos(), typ })? + .get_card() + }; + let Some(ops) = owner.ops_by_atid(value) else { + panic!("{value:?} does not refer to an atom in {owner_id:?} when downcasting {typ}"); + }; + if ops.tid() != TypeId::of::() { + return Err(NotTypAtom { pos: self.pos.clone(), expr: self.ex(), typ }); } let value = A::Data::decode_slice(&mut data); Ok(TAtom { value, untyped: self }) @@ -187,10 +181,6 @@ pub trait AtomMethod: Coding + InHierarchy { const NAME: &str; } -task_local! { - pub(crate) static ATOM_WITHOUT_HANDLE_FINAL_IMPL: Rc>>>; -} - /// A handler for an [AtomMethod] on an [Atomic]. The [AtomMethod] must also be /// registered in [Atomic::reg_methods] pub trait Supports: Atomic { @@ -199,19 +189,6 @@ pub trait Supports: Atomic { hand: Box + '_>, req: M, ) -> impl Future>>; - fn handle_final<'a>( - self, - hand: Box + '_>, - req: M, - ) -> impl Future>> { - async move { - let rcpt = self.handle(hand, req).await; - let _ = ATOM_WITHOUT_HANDLE_FINAL_IMPL.try_with(|cell| cell.replace(Some(Box::new(self)))); - rcpt - } - } - // TODO: default-implement the above somehow while calling OwnedAtom::free if - // necessary } trait HandleAtomMethod { @@ -220,11 +197,6 @@ trait HandleAtomMethod { atom: &'a A, reader: Box + 'a>, ) -> LocalBoxFuture<'a, ()>; - fn handle_final<'a, 'b: 'a>( - &'a self, - atom: A, - reader: Box + 'a>, - ) -> LocalBoxFuture<'a, ()>; } struct AtomMethodHandler(PhantomData, PhantomData); impl> HandleAtomMethod for AtomMethodHandler { @@ -238,16 +210,6 @@ impl> HandleAtomMethod for AtomMethodHandler::handle(atom, reader.finish().await, req).await.unwrap(); }) } - fn handle_final<'a, 'b: 'a>( - &'a self, - atom: A, - mut reader: Box + 'a>, - ) -> LocalBoxFuture<'a, ()> { - Box::pin(async { - let req = reader.read_req::().await.unwrap(); - let _ = Supports::::handle_final(atom, reader.finish().await, req).await.unwrap(); - }) - } } /// A collection of [Supports] impls for an [Atomic]. If a [Supports] @@ -282,20 +244,6 @@ pub(crate) struct MethodSet { handlers: HashMap>>, } impl MethodSet { - pub(crate) async fn final_dispatch<'a>( - &self, - atom: A, - key: Sym, - req: Box + 'a>, - ) -> bool { - match self.handlers.get(&key) { - None => false, - Some(handler) => { - handler.handle_final(atom, req).await; - true - }, - } - } pub(crate) async fn dispatch<'a>( &self, atom: &'_ A, @@ -353,11 +301,11 @@ impl TAtom { pub async fn call>(&self, req: R) -> R::Response where A: Supports<::Root> { R::Response::decode_slice( - &mut &(request(api::Fwd( - self.untyped.atom.clone(), - Sym::parse(::Root::NAME).await.unwrap().tok().to_api(), - enc_vec(&req.into_root()), - ))) + &mut &(request(api::Fwd { + target: self.untyped.atom.clone(), + method: Sym::parse(::Root::NAME).await.unwrap().tok().to_api(), + body: enc_vec(&req.into_root()), + })) .await .unwrap()[..], ) @@ -389,12 +337,6 @@ pub trait AtomOps: 'static { fn call<'a>(&'a self, ctx: AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr>; fn call_ref<'a>(&'a self, ctx: AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr>; fn print<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit>; - fn handle_req<'a>( - &'a self, - ctx: AtomCtx<'a>, - key: Sym, - req: Box + 'a>, - ) -> LocalBoxFuture<'a, bool>; fn handle_req_ref<'a>( &'a self, ctx: AtomCtx<'a>, @@ -417,25 +359,25 @@ pub trait AtomOps: 'static { trait_set! { pub trait AtomFactoryFn = FnOnce() -> LocalBoxFuture<'static, api::LocalAtom> + DynClone; } -pub(crate) struct AtomFactory(Box); +pub(crate) struct AtomFactory(Box, String); impl AtomFactory { - pub fn new(f: impl AsyncFnOnce() -> api::LocalAtom + Clone + 'static) -> Self { - Self(Box::new(|| f().boxed_local())) + pub fn new(name: String, f: impl AsyncFnOnce() -> api::LocalAtom + Clone + 'static) -> Self { + Self(Box::new(|| f().boxed_local()), name) } pub async fn build(self) -> api::LocalAtom { (self.0)().await } } impl Clone for AtomFactory { - fn clone(&self) -> Self { AtomFactory(clone_box(&*self.0)) } + fn clone(&self) -> Self { AtomFactory(clone_box(&*self.0), self.1.clone()) } } impl fmt::Debug for AtomFactory { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AtomFactory") } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AtomFactory<{}>", self.1) } } impl fmt::Display for AtomFactory { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AtomFactory") } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } impl Format for AtomFactory { async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { - "AtomFactory".to_string().into() + self.to_string().into() } } diff --git a/orchid-extension/src/atom_owned.rs b/orchid-extension/src/atom_owned.rs index b8bbe57..36cd99e 100644 --- a/orchid-extension/src/atom_owned.rs +++ b/orchid-extension/src/atom_owned.rs @@ -22,9 +22,8 @@ use task_local::task_local; use crate::gen_expr::{GExpr, bot}; use crate::{ - ATOM_WITHOUT_HANDLE_FINAL_IMPL, AtomCtx, AtomFactory, AtomOps, Atomic, AtomicFeaturesImpl, - AtomicVariant, DynSystemCardExt, Expr, MethodSet, MethodSetBuilder, ToExpr, api, dyn_cted, - err_not_callable, + AtomCtx, AtomFactory, AtomOps, Atomic, AtomicFeaturesImpl, AtomicVariant, DynSystemCardExt, Expr, + MethodSet, MethodSetBuilder, ToExpr, api, dyn_cted, err_not_callable, }; /// Value of [Atomic::Variant] for a type that implements [OwnedAtom] @@ -32,7 +31,7 @@ pub struct OwnedVariant; impl AtomicVariant for OwnedVariant {} impl> AtomicFeaturesImpl for A { fn _factory(self) -> AtomFactory { - AtomFactory::new(async move || { + AtomFactory::new(type_name::().to_string(), async move || { let obj_store = get_obj_store(); let atom_id = { let mut id = obj_store.next_id.borrow_mut(); @@ -73,7 +72,10 @@ impl Deref for AtomReadGuard<'_> { /// Remove an atom from the store pub(crate) async fn take_atom(id: api::AtomId) -> Box { let mut g = get_obj_store().objects.write().await; - g.remove(&id).unwrap_or_else(|| panic!("Received invalid atom ID: {}", id.0)) + g.remove(&id).unwrap_or_else(|| { + let name = dyn_cted().inst().card().name(); + panic!("{name} received invalid atom ID: {}", id.0) + }) } pub(crate) struct OwnedAtomOps { @@ -113,25 +115,6 @@ impl AtomOps for OwnedAtomOps { fn print(&self, AtomCtx(_, id): AtomCtx<'_>) -> LocalBoxFuture<'_, FmtUnit> { Box::pin(async move { AtomReadGuard::new(id.unwrap()).await.dyn_print().await }) } - fn handle_req<'a>( - &'a self, - AtomCtx(_, id): AtomCtx<'a>, - key: Sym, - req: Box + 'a>, - ) -> LocalBoxFuture<'a, bool> { - Box::pin(async move { - let a = take_atom(id.unwrap()).await; - let ms = self.ms.get_or_init(self.msbuild.pack()).await; - let cell = Rc::new(RefCell::new(None)); - let matched = ATOM_WITHOUT_HANDLE_FINAL_IMPL - .scope(cell.clone(), ms.final_dispatch(*a.as_any().downcast().unwrap(), key, req)) - .await; - if let Some(val) = cell.take() { - val.downcast::().unwrap().free().await - } - matched - }) - } fn handle_req_ref<'a>( &'a self, AtomCtx(_, id): AtomCtx<'a>, @@ -303,7 +286,6 @@ fn assert_serializable() { pub(crate) trait DynOwnedAtom: DynClone + 'static { fn as_any_ref(&self) -> &dyn Any; - fn as_any(self: Box) -> Box; fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()>; fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr>; fn dyn_call(self: Box, arg: Expr) -> LocalBoxFuture<'static, GExpr>; @@ -316,7 +298,6 @@ pub(crate) trait DynOwnedAtom: DynClone + 'static { } impl DynOwnedAtom for T { fn as_any_ref(&self) -> &dyn Any { self } - fn as_any(self: Box) -> Box { self } fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()> { async { self.val().await.as_ref().encode(buffer).await.unwrap() }.boxed_local() } diff --git a/orchid-extension/src/atom_thin.rs b/orchid-extension/src/atom_thin.rs index ec2857c..7b9ae61 100644 --- a/orchid-extension/src/atom_thin.rs +++ b/orchid-extension/src/atom_thin.rs @@ -19,7 +19,7 @@ pub struct ThinVariant; impl AtomicVariant for ThinVariant {} impl> AtomicFeaturesImpl for A { fn _factory(self) -> AtomFactory { - AtomFactory::new(async move || { + AtomFactory::new(type_name::().to_string(), async move || { let (id, _) = dyn_cted().inst().card().ops::(); let mut buf = enc_vec(&id); self.encode_vec(&mut buf); @@ -49,7 +49,7 @@ impl AtomOps for ThinAtomOps { fn call_ref<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> { Box::pin(async move { T::decode_slice(&mut &buf[..]).call(arg).await }) } - fn handle_req<'a>( + fn handle_req_ref<'a>( &'a self, AtomCtx(buf, ..): AtomCtx<'a>, key: Sym, @@ -60,14 +60,6 @@ impl AtomOps for ThinAtomOps { ms.dispatch(&T::decode_slice(&mut &buf[..]), key, req).await }) } - fn handle_req_ref<'a>( - &'a self, - ctx: AtomCtx<'a>, - key: Sym, - req: Box + 'a>, - ) -> LocalBoxFuture<'a, bool> { - self.handle_req(ctx, key, req) - } fn serialize<'a, 'b: 'a>( &'a self, ctx: AtomCtx<'a>, diff --git a/orchid-extension/src/cmd_atom.rs b/orchid-extension/src/cmd_atom.rs index d34fa68..c03bbef 100644 --- a/orchid-extension/src/cmd_atom.rs +++ b/orchid-extension/src/cmd_atom.rs @@ -1,23 +1,23 @@ use std::borrow::Cow; +use std::rc::Rc; -use dyn_clone::DynClone; use futures::future::LocalBoxFuture; use never::Never; use orchid_base::{Receipt, ReqHandle, ReqHandleExt}; -use trait_set::trait_set; -use crate::gen_expr::{GExpr, new_atom}; +use crate::gen_expr::{GExpr, new_atom, serialize}; use crate::std_reqs::RunCommand; use crate::{Atomic, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, ToExpr}; -trait_set! { - pub trait ClonableAsyncFnOnceDyn = FnOnce() -> LocalBoxFuture<'static, Option> + DynClone; +pub trait AsyncFnDyn { + fn call<'a>(&'a self) -> LocalBoxFuture<'a, Option>; +} +impl Option> AsyncFnDyn for T { + fn call<'a>(&'a self) -> LocalBoxFuture<'a, Option> { Box::pin(async { (self)().await }) } } -pub struct CmdAtom(Box); -impl Clone for CmdAtom { - fn clone(&self) -> Self { Self(dyn_clone::clone_box(&*self.0)) } -} +#[derive(Clone)] +pub struct CmdAtom(Rc); impl Atomic for CmdAtom { type Data = (); type Variant = OwnedVariant; @@ -29,17 +29,10 @@ impl Supports for CmdAtom { hand: Box + '_>, req: RunCommand, ) -> std::io::Result> { - Self(dyn_clone::clone_box(&*self.0)).handle_final(hand, req).await - } - async fn handle_final<'a>( - self, - hand: Box + '_>, - req: RunCommand, - ) -> std::io::Result> { - let reply = (self.0)().await; + let reply = self.0.call().await; match reply { None => hand.reply(&req, &None).await, - Some(next) => hand.reply(&req, &Some(next.serialize().await)).await, + Some(next) => hand.reply(&req, &Some(serialize(next).await)).await, } } } @@ -48,13 +41,9 @@ impl OwnedAtom for CmdAtom { async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } } -pub fn cmd(f: impl AsyncFnOnce() -> Option + Clone + 'static) -> GExpr { - new_atom(CmdAtom(Box::new(|| { - Box::pin(async { - match f().await { - None => None, - Some(r) => Some(r.to_gen().await), - } - }) +pub fn cmd(f: impl AsyncFn() -> Option + Clone + 'static) -> GExpr { + new_atom(CmdAtom(Rc::new(async move || match f().await { + None => None, + Some(r) => Some(r.to_gen().await), }))) } diff --git a/orchid-extension/src/conv.rs b/orchid-extension/src/conv.rs index d11727b..a758808 100644 --- a/orchid-extension/src/conv.rs +++ b/orchid-extension/src/conv.rs @@ -71,6 +71,14 @@ pub trait ToExpr { where Self: Sized { async { self.to_gen().await.create().await } } + fn boxed<'a>(self) -> Box + where Self: Sized + 'a { + Box::new(self) + } + fn clonable_boxed<'a>(self) -> Box + where Self: Clone + Sized + 'a { + Box::new(self) + } } /// A wrapper for a future that implements [ToExpr] diff --git a/orchid-extension/src/coroutine_exec.rs b/orchid-extension/src/coroutine_exec.rs index 10d9033..57ea931 100644 --- a/orchid-extension/src/coroutine_exec.rs +++ b/orchid-extension/src/coroutine_exec.rs @@ -1,3 +1,4 @@ +use std::any::type_name; use std::borrow::Cow; use std::marker::PhantomData; use std::rc::Rc; @@ -7,9 +8,9 @@ use futures::lock::Mutex; use futures::stream::{self, LocalBoxStream}; use futures::{FutureExt, SinkExt, StreamExt}; use never::Never; -use orchid_base::OrcRes; +use orchid_base::{FmtCtx, FmtUnit, OrcRes}; -use crate::gen_expr::{GExpr, arg, call, lam, new_atom, seq}; +use crate::gen_expr::{GExpr, call, lam, new_atom, seq}; use crate::{Atomic, Expr, OwnedAtom, OwnedVariant, ToExpr, TryFromExpr}; enum Command { @@ -18,6 +19,7 @@ enum Command { } struct BuilderCoroutineData { + name: &'static str, receiver: Mutex>, } @@ -30,7 +32,7 @@ impl BuilderCoroutine { None => panic!("Exec handle dropped and coroutine blocked instead of returning"), Some(Command::Halt(expr)) => expr, Some(Command::Execute(expr, reply)) => - call(lam::<0>(seq(arg(0), call(new_atom(Replier { reply, builder: self }), arg(0)))), expr) + call(lam(async |x| seq(x, call(new_atom(Replier { reply, builder: self }), x)).await), expr) .await, } } @@ -53,6 +55,9 @@ impl OwnedAtom for Replier { std::mem::drop(self.reply); self.builder.run().await } + async fn print_atom<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { + format!("Replier<{}>", self.builder.0.name).into() + } } /// A long-lived async context that can yield to the executor. The expression @@ -62,6 +67,7 @@ pub async fn exec(f: impl for<'a> AsyncFnOnce(ExecHandle<'a>) -> R + let halt = async { Command::Halt(f(ExecHandle(cmd_snd, PhantomData)).await.to_gen().await) }.into_stream(); let coro = BuilderCoroutine(Rc::new(BuilderCoroutineData { + name: type_name::(), receiver: Mutex::new(stream::select(halt, cmd_recv).boxed_local()), })); coro.run().await diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index 1e6e2f7..13ce4f4 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -22,6 +22,7 @@ use orchid_base::{ use substack::Substack; use task_local::task_local; +use crate::gen_expr::serialize; use crate::interner::new_interner; use crate::logger::LoggerImpl; use crate::tree::{TreeIntoApiCtxImpl, get_lazy, with_lazy_member_store}; @@ -63,9 +64,11 @@ pub async fn mute_reply(f: F) -> F::Output { MUTE_REPLY.scope((), f). /// Send a request through the global client's [ClientExt::request] pub async fn request>(t: T) -> T::Response { + let req_str = if MUTE_REPLY.try_with(|b| *b).is_err() { format!("{t:?}") } else { String::new() }; let response = get_client().request(t).await.unwrap(); if MUTE_REPLY.try_with(|b| *b).is_err() { - writeln!(log("msg"), "Got response {response:?}").await; + let ext = dyn_cted().inst().card().name(); + writeln!(log("msg"), "{ext} {req_str} got response {response:?}").await; } response } @@ -342,7 +345,7 @@ impl ExtensionBuilder { api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) => with_sys_record(sys, async { let cnst = get_const(id).await; - handle.reply(fpc, &cnst.serialize().await).await + handle.reply(fpc, &serialize(cnst).await).await }) .await, api::HostExtReq::AtomReq(atom_req) => { @@ -365,16 +368,8 @@ impl ExtensionBuilder { }, api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) => handle.reply(print, &nfo.print(actx).await.to_api()).await, - api::AtomReq::FinalFwded(fwded) => { - let api::FinalFwded(_, key, payload) = &fwded; - let mut reply = Vec::new(); - let key = Sym::from_api(*key).await; - let req = TrivialReqCycle { req: payload, rep: &mut reply }; - let some = nfo.handle_req(actx, key, Box::new(req)).await; - handle.reply(fwded, &some.then_some(reply)).await - }, - api::AtomReq::FwdedRef(fwded) => { - let api::FinalFwded(_, key, payload) = &fwded; + api::AtomReq::Fwded(fwded) => { + let api::Fwded(_, key, payload) = &fwded; let mut reply = Vec::new(); let key = Sym::from_api(*key).await; let req = TrivialReqCycle { req: payload, rep: &mut reply }; @@ -385,7 +380,7 @@ impl ExtensionBuilder { let expr_store = BorrowedExprStore::new(); let expr_handle = ExprHandle::borrowed(*arg, &expr_store); let ret = nfo.call_ref(actx, Expr::from_handle(expr_handle.clone())).await; - let api_expr = ret.serialize().await; + let api_expr = serialize(ret).await; mem::drop(expr_handle); expr_store.dispose().await; handle.reply(call, &api_expr).await @@ -394,7 +389,7 @@ impl ExtensionBuilder { let expr_store = BorrowedExprStore::new(); let expr_handle = ExprHandle::borrowed(*arg, &expr_store); let ret = nfo.call(actx, Expr::from_handle(expr_handle.clone())).await; - let api_expr = ret.serialize().await; + let api_expr = serialize(ret).await; mem::drop(expr_handle); expr_store.dispose().await; handle.reply(call, &api_expr).await diff --git a/orchid-extension/src/expr.rs b/orchid-extension/src/expr.rs index c5d5fbe..8869b82 100644 --- a/orchid-extension/src/expr.rs +++ b/orchid-extension/src/expr.rs @@ -10,7 +10,7 @@ use futures::future::join_all; use hashbrown::HashSet; use orchid_base::{FmtCtx, FmtUnit, Format, OrcErrv, Pos, stash}; -use crate::gen_expr::{GExpr, GExprKind}; +use crate::gen_expr::{GExpr, slot}; use crate::{ForeignAtom, api, notify, request, sys_id}; /// Handle for a lifetime associated with an [ExprHandle], such as a function @@ -158,9 +158,7 @@ impl Expr { pub fn handle(&self) -> Rc { self.handle.clone() } /// Wrap this expression in a [GExpr] synchronously as an escape hatch. /// Otherwise identical to this type's [crate::ToExpr] impl - pub fn slot(&self) -> GExpr { - GExpr { pos: Pos::SlotTarget, kind: GExprKind::Slot(self.clone()) } - } + pub fn slot(&self) -> GExpr { slot(self.clone()) } /// Increments the refcount to ensure that the ticket remains valid even if /// the handle is freed. To avoid a leak, [Expr::deserialize] must eventually /// be called. diff --git a/orchid-extension/src/gen_expr.rs b/orchid-extension/src/gen_expr.rs index a71b6c9..126f4cd 100644 --- a/orchid-extension/src/gen_expr.rs +++ b/orchid-extension/src/gen_expr.rs @@ -1,3 +1,5 @@ +use std::cell::RefCell; +use std::marker::PhantomData; use std::mem; use std::pin::{Pin, pin}; use std::rc::Rc; @@ -6,22 +8,38 @@ use futures::{FutureExt, Stream, StreamExt, stream}; use orchid_base::{ FmtCtx, FmtUnit, Format, OrcErr, OrcErrv, Pos, Sym, Variants, match_mapping, tl_cache, }; +use substack::Substack; +use task_local::task_local; use crate::{AtomFactory, AtomicFeatures, Expr, ToExpr, ToExprFuture, api, request, sys_id}; -/// Newly generated AST. Values of this type should not typically be constructed -/// manually but through the helpers in this module +#[derive(Clone, Copy, Debug)] +struct ExprSerializeCx<'a> { + closures: Substack<'a, u64>, + lambda_counter: &'a RefCell, +} + +/// Release notifications will not be sent for the slots. Use this with +/// messages that imply ownership transfer +pub async fn serialize(expr: GExpr) -> api::Expression { + let cx = ExprSerializeCx { closures: Substack::Bottom, lambda_counter: &RefCell::new(0) }; + expr.serialize(cx).await +} + +/// Smart object representing AST not-yet-sent to the interpreter. This type can +/// be cloned and persisted, and it must not have unbound arguments. The helper +/// functions in this module let you build trees of [ToExpr] implementors which +/// represent lambdas and their arguments separately, and then convert them into +/// [GExpr] in one pass. #[derive(Clone, Debug)] pub struct GExpr { /// AST node type - pub kind: GExprKind, + kind: GExprKind, /// Code location associated with the expression for debugging purposes - pub pos: Pos, + pos: Pos, } impl GExpr { - /// Release notifications will not be sent for the slots. Use this with - /// messages that imply ownership transfer - pub async fn serialize(self) -> api::Expression { + async fn serialize(self, cx: ExprSerializeCx<'_>) -> api::Expression { if let GExprKind::Slot(ex) = self.kind { let hand = ex.handle(); mem::drop(ex); @@ -32,8 +50,8 @@ impl GExpr { } } else { api::Expression { - location: api::Location::Inherit, - kind: self.kind.serialize().boxed_local().await, + location: self.pos.to_api(), + kind: self.kind.serialize(cx).boxed_local().await, } } } @@ -42,7 +60,7 @@ impl GExpr { /// Send the expression to the interpreter to be compiled and to become /// shareable across extensions pub async fn create(self) -> Expr { - Expr::deserialize(request(api::Create(sys_id(), self.serialize().await)).await).await + Expr::deserialize(request(api::Create(sys_id(), serialize(self).await)).await).await } } impl Format for GExpr { @@ -56,8 +74,8 @@ impl Format for GExpr { pub enum GExprKind { /// Function call Call(Box, Box), - /// Lambda expression. Argument numbers are matched when equal - Lambda(u64, Box), + /// Lambda expression. Argument must be the same for slot + Lambda(Box), /// Slot for a lambda argument Arg(u64), /// The second expression is only valid after the first one had already been @@ -80,23 +98,40 @@ pub enum GExprKind { Bottom(OrcErrv), } impl GExprKind { - async fn serialize(self) -> api::ExpressionKind { + pub fn at(self, pos: Pos) -> GExpr { GExpr { kind: self, pos } } + async fn serialize(self, cx: ExprSerializeCx<'_>) -> api::ExpressionKind { match_mapping!(self, Self => api::ExpressionKind { Call( - f => Box::new(f.serialize().await), - x => Box::new(x.serialize().await) + f => Box::new(f.serialize(cx).await), + x => Box::new(x.serialize(cx).await) ), Seq( - a => Box::new(a.serialize().await), - b => Box::new(b.serialize().await) + a => Box::new(a.serialize(cx).await), + b => Box::new(b.serialize(cx).await) ), - Lambda(arg, body => Box::new(body.serialize().await)), - Arg(arg), Const(name.to_api()), Bottom(err.to_api()), NewAtom(fac.clone().build().await), } { - Self::Slot(_) => panic!("processed elsewhere") + Self::Slot(_) => panic!("processed elsewhere"), + Self::Lambda(body) => { + let id: u64; + { + let mut g = cx.lambda_counter.borrow_mut(); + id = *g; + *g += 1; + }; + let cx = ExprSerializeCx { + lambda_counter: cx.lambda_counter, + closures: cx.closures.push(id) + }; + api::ExpressionKind::Lambda(id, + Box::new(body.serialize(cx).await) + ) + }, + Self::Arg(arg) => { + api::ExpressionKind::Arg(*cx.closures.iter().nth(arg as usize).expect("Unbound arg")) + }, }) } } @@ -106,9 +141,9 @@ impl Format for GExprKind { GExprKind::Call(f, x) => tl_cache!(Rc: Rc::new(Variants::default().bounded("{0} ({1})"))) .units([f.print(c).await, x.print(c).await]), - GExprKind::Lambda(arg, body) => - tl_cache!(Rc: Rc::new(Variants::default().bounded("\\{0}.{1}"))) - .units([arg.to_string().into(), body.print(c).await]), + GExprKind::Lambda(body) => + tl_cache!(Rc: Rc::new(Variants::default().bounded("\\{1}"))) + .units([body.print(c).await]), GExprKind::Arg(arg) => arg.to_string().into(), GExprKind::Seq(a, b) => tl_cache!(Rc: Rc::new(Variants::default().bounded("[{0}] {1}"))) @@ -123,7 +158,11 @@ impl Format for GExprKind { } } -fn inherit(kind: GExprKind) -> GExpr { GExpr { pos: Pos::Inherit, kind } } +pub fn inherit(kind: GExprKind) -> GExpr { GExpr { pos: Pos::Inherit, kind } } + +task_local! { + pub static CLOSURE_DEPTH: u64; +} impl ToExpr for Sym { async fn to_expr(self) -> Expr @@ -135,6 +174,8 @@ impl ToExpr for Sym { /// Creates an expression from a new atom that we own. pub fn new_atom(atom: A) -> GExpr { inherit(GExprKind::NewAtom(atom.factory())) } +pub fn slot(expr: Expr) -> GExpr { GExpr { pos: Pos::SlotTarget, kind: GExprKind::Slot(expr) } } + /// An expression which is only valid if a number of dependencies had already /// been normalized pub fn seq( @@ -155,17 +196,49 @@ pub fn seq( }) } -/// Argument bound by an enclosing [lam] or [dyn_lambda] -pub fn arg(n: u64) -> GExpr { inherit(GExprKind::Arg(n)) } - -/// A lambda expression. The difference from [dyn_lambda] is purely aesthetic -pub fn lam(b: impl ToExpr) -> ToExprFuture> { - dyn_lambda(N, b) +#[derive(Debug, Clone, Copy)] +pub enum ArgState { + Building, + Serializing { depth: u64 }, + Ready, } -/// A lambda expression. The difference from [lam] is purely aesthetic -pub fn dyn_lambda(n: u64, b: impl ToExpr) -> ToExprFuture> { - ToExprFuture(async move { inherit(GExprKind::Lambda(n, Box::new(b.to_gen().await))) }) +/// Argument bound by an enclosing [lam] or [dyn_lambda] +#[derive(Debug, Clone, Copy)] +pub struct GenArg<'a>(*const RefCell, PhantomData<&'a ()>); +impl ToExpr for GenArg<'_> { + async fn to_gen(self) -> GExpr { + // SAFETY: Created from a Rc that lives as long as the lifetime arg, see [lam] + let state = unsafe { self.0.as_ref().unwrap() }; + match (*state.borrow(), CLOSURE_DEPTH.try_with(|r| *r)) { + (ArgState::Serializing { .. }, Err(_)) => + panic!("Lambda should have cleared up argstate alongside CLOSURE_DEPTH"), + (ArgState::Serializing { depth }, Ok(total)) => inherit(GExprKind::Arg(total - depth)), + (ArgState::Building, _) => + panic!("Argument serialized before lambda. Likely an over-eager ToExpr impl"), + (ArgState::Ready, _) => + unreachable!("The arg should never be available this long, the GenArg is a convenience"), + } + } +} + +/// A lambda expression. +pub fn lam<'a>( + cb: impl for<'b> AsyncFnOnce(GenArg<'b>) -> GExpr + 'a, +) -> ToExprFuture + 'a> { + let state = Rc::new(RefCell::new(ArgState::Building)); + ToExprFuture(async move { + let rank = CLOSURE_DEPTH.try_with(|r| *r + 1).unwrap_or(0); + match *state.borrow_mut() { + ref mut state @ ArgState::Building => *state = ArgState::Serializing { depth: rank }, + ArgState::Serializing { .. } => panic!("Lambda serialized twice, found interrupted"), + ArgState::Ready => panic!("Lambda serialized twice"), + } + let gen_arg = GenArg(Rc::as_ptr(&state), PhantomData); + let ret = CLOSURE_DEPTH.scope(rank, async { cb(gen_arg).await.to_gen().await }).await; + mem::drop(state); + inherit(GExprKind::Lambda(Box::new(ret))) + }) } /// one or more items that are convertible to expressions. In practice, a diff --git a/orchid-extension/src/std_reqs.rs b/orchid-extension/src/std_reqs.rs index 62ea3f0..82bba39 100644 --- a/orchid-extension/src/std_reqs.rs +++ b/orchid-extension/src/std_reqs.rs @@ -1,6 +1,6 @@ use std::num::NonZero; -use std::time::Duration; +use chrono::{DateTime, Utc}; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; @@ -24,11 +24,11 @@ impl AtomMethod for RunCommand { } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] -pub struct AsDuration; -impl Request for AsDuration { - type Response = Duration; +pub struct AsInstant; +impl Request for AsInstant { + type Response = DateTime; } -impl AtomMethod for AsDuration { +impl AtomMethod for AsInstant { const NAME: &str = "orchid::time::as_duration"; } diff --git a/orchid-extension/src/tree.rs b/orchid-extension/src/tree.rs index d189b8e..e42a450 100644 --- a/orchid-extension/src/tree.rs +++ b/orchid-extension/src/tree.rs @@ -13,7 +13,7 @@ use substack::Substack; use task_local::task_local; use trait_set::trait_set; -use crate::gen_expr::{GExpr, new_atom}; +use crate::gen_expr::{GExpr, new_atom, serialize}; use crate::{BorrowedExprStore, Expr, ExprFunc, ExprHandle, Fun, ToExpr, api}; /// Tokens generated by lexers and parsers @@ -31,7 +31,7 @@ impl TokenVariant for GExpr { async fn from_api(_: api::Expression, _: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self { panic!("Received new expression from host") } - async fn into_api(self, _: &mut Self::ToApiCtx<'_>) -> api::Expression { self.serialize().await } + async fn into_api(self, _: &mut Self::ToApiCtx<'_>) -> api::Expression { serialize(self).await } } impl TokenVariant for Expr { @@ -193,7 +193,7 @@ impl LazyMemKind { pub(crate) async fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::MemberKind { match self { Self::Lazy(lazy) => api::MemberKind::Lazy(add_lazy(ctx, lazy)), - Self::Const(c) => api::MemberKind::Const(c.serialize().await), + Self::Const(c) => api::MemberKind::Const(serialize(c).await), Self::Mod(members) => api::MemberKind::Module(api::Module { members: stream(async |mut cx| { for m in members { diff --git a/orchid-host/Cargo.toml b/orchid-host/Cargo.toml index 7958c06..800319b 100644 --- a/orchid-host/Cargo.toml +++ b/orchid-host/Cargo.toml @@ -10,6 +10,7 @@ async-event = "0.2.1" async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" } async-once-cell = "0.5.4" bound = "0.6.0" +chrono = "0.4.44" derive_destructure = "1.0.0" futures = { version = "0.3.31", features = ["std"], default-features = false } futures-locks = "0.7.1" diff --git a/orchid-host/src/atom.rs b/orchid-host/src/atom.rs index f2f76b4..cbf65ac 100644 --- a/orchid-host/src/atom.rs +++ b/orchid-host/src/atom.rs @@ -94,7 +94,7 @@ impl AtomHand { #[must_use] pub fn ext(&self) -> &Extension { self.sys().ext() } pub async fn req(&self, key: api::TStrv, req: Vec) -> Option> { - self.0.owner.client().request(api::FinalFwded(self.0.api_ref(), key, req)).await.unwrap() + self.0.owner.client().request(api::Fwded(self.0.api_ref(), key, req)).await.unwrap() } #[must_use] pub fn api_ref(&self) -> api::Atom { self.0.api_ref() } diff --git a/orchid-host/src/cmd_system.rs b/orchid-host/src/cmd_system.rs index 7172c9e..46c7f59 100644 --- a/orchid-host/src/cmd_system.rs +++ b/orchid-host/src/cmd_system.rs @@ -7,40 +7,52 @@ use std::rc::Rc; use async_event::Event; use async_fn_stream::stream; +use chrono::{DateTime, Utc}; use futures::channel::mpsc; use futures::future::LocalBoxFuture; use futures::stream::FuturesUnordered; use futures::{SinkExt, StreamExt, select}; use never::Never; -use orchid_base::{OrcErrv, Receipt, ReqHandle, Sym}; -use orchid_extension::{self as ox, AtomicFeatures as _}; +use orchid_base::{OrcErrv, Receipt, ReqHandle, Sym, fmt, is, log, mk_errv}; +use orchid_extension::{self as ox, AtomicFeatures as _, get_arg}; use crate::ctx::Ctx; use crate::execute::{ExecCtx, ExecResult}; use crate::expr::{Expr, ExprFromApiCtx, PathSetBuilder}; +use crate::extension::Extension; +use crate::inline::ext_inline; +use crate::system::System; use crate::tree::Root; +/// Events internally recognized by this system sent through [CommandQueue] +enum Task { + RunCommand(Expr), + Sleep(DateTime, Expr), + Exit, +} + struct CommandQueueState { - new: VecDeque, + new: VecDeque, added: Rc, - wants_exit: bool, ctx: Ctx, } + +/// Shared object serving as a communication point between the extension +/// [CmdSystemCtor] and the host toolkit [CmdRunner] #[derive(Clone)] struct CommandQueue(Rc>); impl CommandQueue { fn new(ctx: Ctx, init: impl IntoIterator) -> Self { Self(Rc::new(RefCell::new(CommandQueueState { - new: init.into_iter().collect(), + new: init.into_iter().map(Task::RunCommand).collect(), added: Rc::default(), - wants_exit: false, ctx, }))) } - pub fn push(&self, expr: Expr) { + pub fn push(&self, task: Task) { let was_empty = { let mut g = self.0.borrow_mut(); - g.new.push_back(expr); + g.new.push_back(task); g.new.len() == 1 }; if was_empty { @@ -48,7 +60,7 @@ impl CommandQueue { added.notify_one(); } } - pub async fn get_new(&self) -> Expr { + pub async fn get_new(&self) -> Task { let added = { let mut g = self.0.borrow_mut(); if let Some(waiting) = g.new.pop_front() { @@ -65,10 +77,13 @@ impl Debug for CommandQueue { } } -pub enum CmdResult { - /// All command sequences settled +/// Events the embedder may want to be notified about +pub enum CmdEvent { + /// All commands finished and there's nothing else to do Settled, - /// Exit was requested explicitly by usercode + /// Exit was requested explicitly by usercode. This request means that all + /// internal system state should be discarded, so if it is returned by the + /// [CmdRunner], no further commands are permitted Exit, /// Ran out of gas Gas, @@ -89,35 +104,64 @@ pub struct CmdRunner { queue: CommandQueue, gas: Option, interrupted: Option, - futures: FuturesUnordered>>, + system: System, + futures: FuturesUnordered>>, } impl CmdRunner { pub async fn new(root: Root, ctx: Ctx, init: impl IntoIterator) -> Self { - Self { - futures: FuturesUnordered::new(), - gas: None, - root, - interrupted: None, - queue: CommandQueue::new(ctx, init), - } + let queue = CommandQueue::new(ctx.clone(), init); + let ext_builder = ox::ExtensionBuilder::new("orchid::cmd").system(CmdSystemCtor(queue.clone())); + let extension = (Extension::new(ext_inline(ext_builder, ctx.clone()).await, ctx).await) + .expect("IO error on in-memory pipe"); + let system_ctor = (extension.system_ctors().find(|ctor| ctor.decl.name == "orchid::cmd")) + .expect("Missing command system ctor"); + let (cmd_root, system) = system_ctor.run(vec![]).await; + let root = root.merge(&cmd_root).await.expect("Could not merge command system into tree"); + Self { futures: FuturesUnordered::new(), gas: None, root, interrupted: None, queue, system } } #[must_use] + pub fn sys(&self) -> &System { &self.system } + #[must_use] pub fn get_gas(&self) -> u64 { self.gas.expect("queried gas but no gas was set") } pub fn set_gas(&mut self, gas: u64) { self.gas = Some(gas) } pub fn disable_gas(&mut self) { self.gas = None } - pub async fn execute(&mut self) -> CmdResult { + pub async fn execute(&mut self) -> CmdEvent { let waiting_on_queue = RefCell::new(false); - let (mut spawn, mut on_spawn) = mpsc::channel(1); + let (mut spawn, mut on_spawn) = mpsc::channel::>>(1); let mut normalize_stream = pin!( stream(async |mut h| { loop { - if self.queue.0.borrow().wants_exit { - h.emit(CmdResult::Exit).await; - break; - } waiting_on_queue.replace(false); let mut xctx = match self.interrupted.take() { - None => ExecCtx::new(self.root.clone(), self.queue.get_new().await).await, + None => match self.queue.get_new().await { + Task::RunCommand(expr) => ExecCtx::new(self.root.clone(), expr).await, + Task::Sleep(until, expr) => { + let queue = self.queue.clone(); + let ctx = queue.0.borrow_mut().ctx.clone(); + spawn + .send(Box::pin(async move { + let delta = until - Utc::now(); + match delta.to_std() { + Err(_) => + writeln!( + log("debug"), + "Negative sleep found ({delta}), requeuing as instant" + ) + .await, + Ok(delay) => ctx.sleep(delay).await, + }; + queue.push(Task::RunCommand(expr)); + None + })) + .await + .expect("Receiver stored in parent future"); + continue; + }, + Task::Exit => { + h.emit(CmdEvent::Exit).await; + break; + }, + }, Some(xctx) => xctx, }; waiting_on_queue.replace(true); @@ -126,16 +170,16 @@ impl CmdRunner { match res { ExecResult::Err(e, gas) => { self.gas = gas; - h.emit(CmdResult::Err(e)).await; + h.emit(CmdEvent::Err(e)).await; }, ExecResult::Gas(exec) => { self.interrupted = Some(exec); - h.emit(CmdResult::Gas).await; + h.emit(CmdEvent::Gas).await; }, ExecResult::Value(val, gas) => { self.gas = gas; let Some(atom) = val.as_atom().await else { - h.emit(CmdResult::NonCommand(val)).await; + h.emit(CmdEvent::NonCommand(val)).await; continue; }; let queue = self.queue.clone(); @@ -143,11 +187,13 @@ impl CmdRunner { spawn .send(Box::pin(async move { match atom.ipc(ox::std_reqs::RunCommand).await { - None => Some(CmdResult::NonCommand(val)), + None => Some(CmdEvent::NonCommand(val)), Some(None) => None, Some(Some(expr)) => { let from_api_cx = ExprFromApiCtx { ctx, sys: atom.api_ref().owner }; - queue.push(Expr::from_api(expr, PathSetBuilder::new(), from_api_cx).await); + queue.push(Task::RunCommand( + Expr::from_api(expr, PathSetBuilder::new(), from_api_cx).await, + )); None }, } @@ -161,17 +207,20 @@ impl CmdRunner { .fuse() ); loop { - if self.queue.0.borrow().wants_exit { - break CmdResult::Exit; - } let task = select!( r_opt = self.futures.by_ref().next() => match r_opt { Some(Some(r)) => break r, - None if *waiting_on_queue.borrow() => break CmdResult::Settled, + None if *waiting_on_queue.borrow() => break CmdEvent::Settled, None | Some(None) => continue, }, - r = normalize_stream.by_ref().next() => break r.expect("infinite stream"), - task = on_spawn.by_ref().next() => task.expect("sender moved into infinite stream"), + r = normalize_stream.by_ref().next() => match r { + None => break CmdEvent::Exit, + Some(r) => break r, + }, + task = on_spawn.by_ref().next() => match task { + None => break CmdEvent::Exit, + Some(r) => r, + }, ); self.futures.push(task) } @@ -189,9 +238,7 @@ impl ox::SystemCard for CmdSystemCard { } #[derive(Debug)] -pub struct CmdSystemCtor { - queue: CommandQueue, -} +pub struct CmdSystemCtor(CommandQueue); impl ox::SystemCtor for CmdSystemCtor { const NAME: &'static str = "orchid::cmd"; const VERSION: f64 = 0.1; @@ -199,7 +246,7 @@ impl ox::SystemCtor for CmdSystemCtor { type Deps = (); type Instance = CmdSystemInst; fn inst(&self, _: ::Sat) -> Self::Instance { - CmdSystemInst { queue: self.queue.clone() } + CmdSystemInst { queue: self.0.clone() } } } @@ -217,12 +264,39 @@ impl ox::System for CmdSystemInst { ox::tree::fun(true, "spawn", async |side: ox::Expr, cont: ox::Expr| { ox::cmd(async move || { let queue = ox_get_queue(); - let side_xtk = side.serialize().await; + let side_xtk = side.clone().serialize().await; let mut g = queue.0.borrow_mut(); let host_ex = g.ctx.exprs.take_expr(side_xtk).expect("Host could not locate leaked expr by ID "); - g.new.push_back(host_ex); - Some(cont) + g.new.push_back(Task::RunCommand(host_ex)); + Some(cont.clone()) + }) + }), + ox::tree::cnst(true, "yield", ox::cmd(async || None::)), + ox::tree::cnst( + true, + "exit", + ox::cmd(async || { + ox_get_queue().push(Task::Exit); + None:: + }), + ), + ox::tree::fun(true, "sleep", async |until: ox::ForeignAtom, cont: ox::Expr| { + let Some(until) = until.call(ox::std_reqs::AsInstant).await else { + return ox::gen_expr::bot(mk_errv( + is("Not an instant").await, + format!("{} is not an instant", fmt(&until).await), + [get_arg(0).pos().await], + )); + }; + ox::cmd(async move || { + let queue = ox_get_queue(); + let cont_xtk = cont.clone().serialize().await; + let mut g = queue.0.borrow_mut(); + let host_ex = + g.ctx.exprs.take_expr(cont_xtk).expect("Host could not locate leaked expr by ID "); + g.new.push_back(Task::Sleep(until, host_ex)); + None:: }) }), ]) diff --git a/orchid-host/src/ctx.rs b/orchid-host/src/ctx.rs index f1408a9..a530c52 100644 --- a/orchid-host/src/ctx.rs +++ b/orchid-host/src/ctx.rs @@ -18,7 +18,8 @@ pub trait JoinHandle { /// It is guaranteed that the future will never be polled after this is called fn abort(&self); /// take the future out of the task. If the return value - /// is dropped, the spawned future is also dropped + /// is dropped, the spawned future is also dropped. The returned boxed future + /// will finish any sleep requested at spawn. fn join(self: Box) -> LocalBoxFuture<'static, ()>; } @@ -30,6 +31,9 @@ pub trait Spawner { /// exit while there are pending tasks to allow external communication /// channels to cleanly shut down. fn spawn_obj(&self, delay: Duration, fut: LocalBoxFuture<'static, ()>) -> Box; + fn sleep(&self, delay: Duration) -> LocalBoxFuture<'static, ()> { + self.spawn_obj(delay, Box::pin(async move {})).join() + } } pub struct CtxData { @@ -79,6 +83,9 @@ impl Ctx { ) -> Box { self.spawner.spawn_obj(delay, Box::pin(fut)) } + /// Return after an amount of time has passed + #[must_use] + pub async fn sleep(&self, delay: Duration) { self.spawner.sleep(delay).await } #[must_use] pub(crate) async fn system_inst(&self, id: api::SysId) -> Option { self.systems.read().await.get(&id).and_then(WeakSystem::upgrade) diff --git a/orchid-host/src/extension.rs b/orchid-host/src/extension.rs index 6a2e075..a8d92a3 100644 --- a/orchid-host/src/extension.rs +++ b/orchid-host/src/extension.rs @@ -27,7 +27,7 @@ use crate::ctx::{Ctx, JoinHandle}; use crate::dealias::{ChildError, ChildErrorKind, walk}; use crate::expr::{Expr, ExprFromApiCtx, PathSetBuilder}; use crate::system::SystemCtor; -use crate::tree::MemberKind; +use crate::tree::MemKind; pub struct ExtPort { pub input: Pin>, @@ -54,17 +54,19 @@ pub struct ExtensionData { strings: RefCell>, string_vecs: RefCell>, /// Moved over from [ExtPort] to allow hooking to the extension's drop - _drop_trigger: Box, + drop_trigger: Box, } impl Drop for ExtensionData { fn drop(&mut self) { let client = self.client.clone(); let join_ext = self.join_ext.take().expect("Only called once in Drop"); let comm_cx = self.comm_cx.take().expect("Only used here"); + let drop_trigger = std::mem::replace(&mut self.drop_trigger, Box::new(())); stash(async move { client.notify(api::HostExtNotif::Exit).await.unwrap(); comm_cx.exit().await.unwrap(); join_ext.join().await; + std::mem::drop(drop_trigger); }) } } @@ -158,12 +160,12 @@ impl Extension { handle.reply(&vi, &markerv).await }, }, - api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => { + api::ExtHostReq::Fwd(ref fw @ api::Fwd { ref target, ref method, ref body }) => { let sys = - ctx.system_inst(atom.owner).await.expect("owner of live atom dropped"); + ctx.system_inst(target.owner).await.expect("owner of live atom dropped"); let client = sys.client(); let reply = client - .request(api::FinalFwded(fw.0.clone(), *key, body.clone())) + .request(api::Fwded(target.clone(), *method, body.clone())) .await .unwrap(); handle.reply(fw, &reply).await @@ -229,8 +231,8 @@ impl Extension { 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, + MemKind::Const => api::MemberInfoKind::Constant, + MemKind::Module(_) => api::MemberInfoKind::Module, }; members.insert(k.to_api(), api::MemberInfo { public: v.public, kind }); } @@ -293,7 +295,7 @@ impl Extension { client: Rc::new(client), strings: RefCell::default(), string_vecs: RefCell::default(), - _drop_trigger: init.drop_trigger, + drop_trigger: init.drop_trigger, } }))) } diff --git a/orchid-host/src/lex.rs b/orchid-host/src/lex.rs index de158ac..21ffdd6 100644 --- a/orchid-host/src/lex.rs +++ b/orchid-host/src/lex.rs @@ -262,8 +262,9 @@ pub async fn sys_lex(ctx: &mut LexCtx<'_>) -> Option>> { }) .await; match lx { - Err(e) => - return Some(Err(errors.into_iter().fold(OrcErrv::from_api(e).await, |a, b| a + b))), + Err(e) => { + return Some(Err(errors.into_iter().fold(OrcErrv::from_api(e).await, |a, b| a + b))); + }, Ok(Some(lexed)) => { ctx.set_pos(lexed.pos); let mut stable_trees = Vec::new(); diff --git a/orchid-host/src/lib.rs b/orchid-host/src/lib.rs index db26601..a2a1935 100644 --- a/orchid-host/src/lib.rs +++ b/orchid-host/src/lib.rs @@ -1,9 +1,9 @@ use orchid_api as api; pub mod atom; -pub mod ctx; #[cfg(feature = "orchid-extension")] pub mod cmd_system; +pub mod ctx; pub mod dealias; #[cfg(feature = "tokio")] pub mod dylib; diff --git a/orchid-host/src/parse.rs b/orchid-host/src/parse.rs index d38a8bc..6fae932 100644 --- a/orchid-host/src/parse.rs +++ b/orchid-host/src/parse.rs @@ -8,7 +8,7 @@ use substack::Substack; use crate::ctx::Ctx; use crate::expr::Expr; -use crate::parsed::{Item, ItemKind, ParsedMember, ParsedMemberKind, ParsedModule}; +use crate::parsed::{Item, ItemKind, ParsedMemKind, ParsedMember, ParsedModule}; use crate::system::System; type ParsSnippet<'a> = Snippet<'a, Expr, Expr>; @@ -101,7 +101,7 @@ pub async fn parse_exportable_item<'a>( ) -> OrcRes> { let kind = if discr == is("mod").await { let (name, body) = parse_module(ctx, path, tail).await?; - ItemKind::Member(ParsedMember { name, exported, kind: ParsedMemberKind::Mod(body) }) + ItemKind::Member(ParsedMember { name, exported, kind: ParsedMemKind::Mod(body) }) } else if let Some(parser) = ctx.systems().find_map(|s| s.get_parser(discr.clone())) { return parser .parse(ctx, path, tail.to_vec(), exported, comments, &mut async |stack, lines| { diff --git a/orchid-host/src/parsed.rs b/orchid-host/src/parsed.rs index 5c01522..d8d23b7 100644 --- a/orchid-host/src/parsed.rs +++ b/orchid-host/src/parsed.rs @@ -53,10 +53,10 @@ impl Format for Item { let item_text = match &self.kind { ItemKind::Import(i) => format!("import {i}").into(), ItemKind::Member(mem) => match &mem.kind { - ParsedMemberKind::Const(_, sys) => + ParsedMemKind::Const(_, sys) => tl_cache!(Rc: Rc::new(Variants::default().bounded("const {0} via {1}"))) .units([mem.name.to_string().into(), sys.print(c).await]), - ParsedMemberKind::Mod(module) => + ParsedMemKind::Mod(module) => tl_cache!(Rc: Rc::new(Variants::default().bounded("module {0} {{\n\t{1}\n}}"))) .units([mem.name.to_string().into(), module.print(c).boxed_local().await]), }, @@ -69,12 +69,12 @@ impl Format for Item { pub struct ParsedMember { pub name: IStr, pub exported: bool, - pub kind: ParsedMemberKind, + pub kind: ParsedMemKind, } impl ParsedMember { #[must_use] pub fn name(&self) -> IStr { self.name.clone() } - pub fn new(exported: bool, name: IStr, kind: impl Into) -> Self { + pub fn new(exported: bool, name: IStr, kind: impl Into) -> Self { Self { exported, name, kind: kind.into() } } } @@ -101,11 +101,11 @@ impl fmt::Debug for ParsedExpr { } #[derive(Debug)] -pub enum ParsedMemberKind { +pub enum ParsedMemKind { Const(api::ParsedConstId, System), Mod(ParsedModule), } -impl From for ParsedMemberKind { +impl From for ParsedMemKind { fn from(value: ParsedModule) -> Self { Self::Mod(value) } } #[derive(Debug, Default)] @@ -137,7 +137,7 @@ impl ParsedModule { .filter_map(|it| if let ItemKind::Import(i) = &it.kind { Some(i) } else { None }) } pub fn default_item(self, name: IStr, sr: SrcRange) -> Item { - let mem = ParsedMember { exported: true, name, kind: ParsedMemberKind::Mod(self) }; + let mem = ParsedMember { exported: true, name, kind: ParsedMemKind::Mod(self) }; Item { comments: vec![], sr, kind: ItemKind::Member(mem) } } } @@ -157,8 +157,8 @@ impl Tree for ParsedModule { .find(|m| m.name == key) { match &member.kind { - ParsedMemberKind::Const(..) => return ChildResult::Err(ChildErrorKind::Constant), - ParsedMemberKind::Mod(m) => return ChildResult::Ok(m), + ParsedMemKind::Const(..) => return ChildResult::Err(ChildErrorKind::Constant), + ParsedMemKind::Mod(m) => return ChildResult::Ok(m), } } ChildResult::Err(ChildErrorKind::Missing) diff --git a/orchid-host/src/sys_parser.rs b/orchid-host/src/sys_parser.rs index 5d58334..df12e09 100644 --- a/orchid-host/src/sys_parser.rs +++ b/orchid-host/src/sys_parser.rs @@ -9,7 +9,7 @@ use crate::expr::ExprFromApiCtx; use crate::expr_store::ExprStore; use crate::parse::HostParseCtx; use crate::parsed::{ - Item, ItemKind, ParsTokTree, ParsedMember, ParsedMemberKind, ParsedModule, tt_to_api, + Item, ItemKind, ParsTokTree, ParsedMemKind, ParsedMember, ParsedModule, tt_to_api, }; use crate::system::System; @@ -88,11 +88,11 @@ async fn conv( let mkind = match kind { api::ParsedMemberKind::Module { lines, use_prelude } => { let items = conv(lines, mem_path, callback, ctx).boxed_local().await?; - ParsedMemberKind::Mod(ParsedModule::new(use_prelude, items)) + ParsedMemKind::Mod(ParsedModule::new(use_prelude, items)) }, api::ParsedMemberKind::Constant(cid) => { ctx.sys.0.const_paths.insert(cid, ctx.mod_path.suffix(mem_path.unreverse()).await); - ParsedMemberKind::Const(cid, ctx.sys.clone()) + ParsedMemKind::Const(cid, ctx.sys.clone()) }, }; items.push(Item { diff --git a/orchid-host/src/tree.rs b/orchid-host/src/tree.rs index abcf877..2aedded 100644 --- a/orchid-host/src/tree.rs +++ b/orchid-host/src/tree.rs @@ -21,11 +21,11 @@ use crate::api; use crate::ctx::Ctx; use crate::dealias::{ChildErrorKind, Tree, absolute_path, resolv_glob, walk}; use crate::expr::{Expr, ExprFromApiCtx, PathSetBuilder}; -use crate::parsed::{ItemKind, ParsedMemberKind, ParsedModule}; +use crate::parsed::{ItemKind, ParsedMemKind, ParsedModule}; use crate::system::System; pub struct RootData { - pub root: Module, + pub root: Mod, pub consts: MemoMap, pub ctx: Ctx, } @@ -34,17 +34,13 @@ pub struct Root(pub Rc>); impl Root { #[must_use] pub fn new(ctx: Ctx) -> Self { - Root(Rc::new(RwLock::new(RootData { - root: Module::default(), - consts: MemoMap::default(), - ctx, - }))) + Root(Rc::new(RwLock::new(RootData { root: Mod::default(), consts: MemoMap::default(), ctx }))) } #[must_use] pub async fn from_api(api: api::Module, sys: &System) -> Self { let consts = MemoMap::new(); let mut tfac = TreeFromApiCtx { consts: &consts, path: iv(&[][..]).await, sys }; - let root = Module::from_api(api, &mut tfac).await; + let root = Mod::from_api(api, &mut tfac).await; Root(Rc::new(RwLock::new(RootData { root, consts, ctx: sys.ctx().clone() }))) } pub async fn merge(&self, new: &Root) -> Result { @@ -71,14 +67,14 @@ impl Root { root: &this.root, ctx: &this.ctx, }; - let mut module = Module::from_parsed(parsed, pars_prefix.clone(), &mut tfpctx).await; + let mut module = Mod::from_parsed(parsed, pars_prefix.clone(), &mut tfpctx).await; for step in pars_prefix.iter().rev() { - let kind = OnceCell::from(MemberKind::Module(module)); + let kind = OnceCell::from(MemKind::Module(module)); let members = HashMap::from([( step.clone(), Rc::new(Member { public: true, lazy: RefCell::new(None), kind }), )]); - module = Module { imports: HashMap::new(), members } + module = Mod { imports: HashMap::new(), members } } let root = (this.root.merge(&module, this.ctx.clone(), &consts).await) .expect("Merge conflict between parsed and existing module"); @@ -159,11 +155,11 @@ pub struct ResolvedImport { } #[derive(Clone, Default)] -pub struct Module { +pub struct Mod { pub imports: HashMap>>, pub members: HashMap>, } -impl Module { +impl Mod { #[must_use] pub async fn from_api(api: api::Module, ctx: &mut TreeFromApiCtx<'_>) -> Self { let mut members = HashMap::new(); @@ -178,11 +174,11 @@ impl Module { let cx = ExprFromApiCtx { ctx: ctx.sys.ctx().clone(), sys: ctx.sys.id() }; let expr = Expr::from_api(val, PathSetBuilder::new(), cx).await; ctx.consts.insert(name.clone(), expr); - (None, Some(MemberKind::Const)) + (None, Some(MemKind::Const)) }, api::MemberKind::Module(m) => { let m = Self::from_api(m, &mut ctx.push(mem_name.clone()).await).boxed_local().await; - (None, Some(MemberKind::Module(m))) + (None, Some(MemKind::Module(m))) }, }; members.insert( @@ -305,7 +301,7 @@ impl Module { match &item.kind { ItemKind::Member(mem) => { let path = path.to_vname().suffix([mem.name.clone()]).to_sym().await; - let kind = OnceCell::from(MemberKind::from_parsed(&mem.kind, path.clone(), ctx).await); + let kind = OnceCell::from(MemKind::from_parsed(&mem.kind, path.clone(), ctx).await); members.insert( mem.name.clone(), Rc::new(Member { kind, lazy: RefCell::default(), public: mem.exported }), @@ -314,14 +310,14 @@ impl Module { ItemKind::Import(_) => (), } } - Module { imports, members } + Mod { imports, members } } pub async fn merge( &self, - other: &Module, + other: &Mod, ctx: Ctx, consts: &MemoMap, - ) -> Result { + ) -> Result { if !self.imports.is_empty() || !other.imports.is_empty() { return Err(MergeErr { path: VPath::new([]), kind: MergeErrKind::Imports }); } @@ -335,7 +331,7 @@ impl Module { return Err(MergeErr { path: VPath::new([]), kind: MergeErrKind::Visibility }); } match (own.kind(ctx.clone(), consts).await, mem.kind(ctx.clone(), consts).await) { - (MemberKind::Module(own_sub), MemberKind::Module(sub)) => { + (MemKind::Module(own_sub), MemKind::Module(sub)) => { match own_sub.merge(sub, ctx.clone(), consts).boxed_local().await { Ok(module) => { members.insert( @@ -343,7 +339,7 @@ impl Module { Rc::new(Member { lazy: RefCell::new(None), public: own.public, - kind: OnceCell::from(MemberKind::Module(module)), + kind: OnceCell::from(MemKind::Module(module)), }), ); }, @@ -361,7 +357,7 @@ impl Module { slot.insert(mem.clone()); } } - Ok(Module { imports: HashMap::new(), members }) + Ok(Mod { imports: HashMap::new(), members }) } } @@ -380,13 +376,13 @@ pub enum MergeErrKind { pub struct FromParsedCtx<'a> { pars_prefix: Sym, pars_root: &'a ParsedModule, - root: &'a Module, + root: &'a Mod, ctx: &'a Ctx, consts: &'a MemoMap, deferred_consts: &'a mut Vec<(Sym, api::SysId, api::ParsedConstId)>, } -impl Tree for Module { +impl Tree for Mod { type Ctx<'a> = (Ctx, &'a MemoMap); async fn child( &self, @@ -401,8 +397,8 @@ impl Tree for Module { return Err(ChildErrorKind::Private); } match &member.kind(ctx.clone(), consts).await { - MemberKind::Module(m) => Ok(m), - MemberKind::Const => Err(ChildErrorKind::Constant), + MemKind::Module(m) => Ok(m), + MemKind::Const => Err(ChildErrorKind::Constant), } } fn children(&self, public_only: bool) -> hashbrown::HashSet { @@ -413,11 +409,11 @@ impl Tree for Module { pub struct Member { pub public: bool, pub lazy: RefCell>, - pub kind: OnceCell, + pub kind: OnceCell, } impl Member { #[must_use] - pub async fn kind<'a>(&'a self, ctx: Ctx, consts: &MemoMap) -> &'a MemberKind { + pub async fn kind<'a>(&'a self, ctx: Ctx, consts: &MemoMap) -> &'a MemKind { (self.kind.get_or_init(async { let handle = self.lazy.borrow_mut().take().expect("If kind is uninit, lazy must be Some"); handle.run(ctx, consts).await @@ -426,20 +422,20 @@ impl Member { } } -pub enum MemberKind { +pub enum MemKind { Const, - Module(Module), + Module(Mod), } -impl MemberKind { +impl MemKind { #[must_use] - async fn from_parsed(parsed: &ParsedMemberKind, path: Sym, ctx: &mut FromParsedCtx<'_>) -> Self { + async fn from_parsed(parsed: &ParsedMemKind, path: Sym, ctx: &mut FromParsedCtx<'_>) -> Self { match parsed { - ParsedMemberKind::Const(id, sys) => { + ParsedMemKind::Const(id, sys) => { ctx.deferred_consts.push((path, sys.id(), *id)); - MemberKind::Const + MemKind::Const }, - ParsedMemberKind::Mod(m) => - MemberKind::Module(Module::from_parsed(m, path, ctx).boxed_local().await), + ParsedMemKind::Mod(m) => + MemKind::Module(Mod::from_parsed(m, path, ctx).boxed_local().await), } } } @@ -452,7 +448,7 @@ pub struct LazyMemberHandle { } impl LazyMemberHandle { #[must_use] - pub async fn run(mut self, ctx: Ctx, consts: &MemoMap) -> MemberKind { + pub async fn run(mut self, ctx: Ctx, consts: &MemoMap) -> MemKind { let sys = ctx.system_inst(self.sys).await.expect("Missing system for lazy member"); match sys.get_tree(self.id).await { api::MemberKind::Const(c) => { @@ -460,12 +456,12 @@ impl LazyMemberHandle { let expr = Expr::from_api(c, PathSetBuilder::new(), ctx).await; let (.., path) = self.destructure(); consts.insert(path, expr); - MemberKind::Const + MemKind::Const }, api::MemberKind::Module(m) => { let (.., path) = self.destructure(); - MemberKind::Module( - Module::from_api(m, &mut TreeFromApiCtx { sys: &sys, consts, path: path.tok() }).await, + MemKind::Module( + Mod::from_api(m, &mut TreeFromApiCtx { sys: &sys, consts, path: path.tok() }).await, ) }, api::MemberKind::Lazy(id) => { diff --git a/orchid-std/src/macros/instantiate_tpl.rs b/orchid-std/src/macros/instantiate_tpl.rs index 0856338..c8e7bad 100644 --- a/orchid-std/src/macros/instantiate_tpl.rs +++ b/orchid-std/src/macros/instantiate_tpl.rs @@ -2,9 +2,8 @@ use std::borrow::Cow; use never::Never; use orchid_base::fmt; -use orchid_extension::Expr; use orchid_extension::gen_expr::new_atom; -use orchid_extension::{Atomic, OwnedAtom, OwnedVariant, TAtom, ToExpr, exec}; +use orchid_extension::{Atomic, Expr, OwnedAtom, OwnedVariant, TAtom, ToExpr, exec}; use crate::macros::mactree::{MacTok, MacTree}; diff --git a/orchid-std/src/macros/let_line.rs b/orchid-std/src/macros/let_line.rs index 470e7e8..8947632 100644 --- a/orchid-std/src/macros/let_line.rs +++ b/orchid-std/src/macros/let_line.rs @@ -4,16 +4,16 @@ use futures::{FutureExt, StreamExt, stream}; use hashbrown::HashMap; use itertools::Itertools; use orchid_base::{ - Comment, OrcRes, Paren, Parsed, Snippet, Sym, expect_tok, is, report, sym, token_errv, + Comment, OrcRes, Paren, Parsed, Snippet, Sym, expect_tok, fmt, is, report, token_errv, try_pop_no_fluff, with_reporter, }; -use orchid_extension::gen_expr::{call, new_atom}; use orchid_extension::{ ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser, TAtom, TryFromExpr, }; use crate::macros::mactree::{MacTok, MacTree, MacTreeSeq}; use crate::macros::ph_lexer::PhAtom; +use crate::macros::resolve::{ArgStack, resolve}; #[derive(Default)] pub struct LetLine; @@ -35,10 +35,16 @@ impl Parser for LetLine { }; let Parsed { tail, .. } = expect_tok(tail, is("=").await).await?; let aliased = parse_tokv(tail).await; + eprintln!("Parsed tokv: {}", fmt(&aliased).await); Ok(vec![ParsedLine::cnst(&line.sr(), &comments, exported, name, async move |ctx| { let macro_input = MacTok::S(Paren::Round, with_reporter(dealias_mac_v(&aliased, &ctx)).await?).at(sr.pos()); - Ok(call(sym!(macros::resolve), new_atom(macro_input))) + eprintln!("Dealiased tokv: {}", fmt(¯o_input).await); + let gx = resolve(macro_input, ArgStack::end()).await; + eprintln!("resolves to: {}", fmt(&gx).await); + let x = gx.create().await; + eprintln!("created as: {}", fmt(&x).await); + Ok(x) })]) } } @@ -63,6 +69,7 @@ pub async fn dealias_mac_v(aliased: &MacTreeSeq, ctx: &ConstCtx) -> MacTreeSeq { pub async fn parse_tokv(line: PSnippet<'_>) -> MacTreeSeq { if let Some((idx, arg)) = line.iter().enumerate().find_map(|(i, x)| Some((i, x.as_lambda()?))) { + eprintln!("Found a lambda while parsing tokv"); let (head, lambda) = line.split_at(idx as u32); let (_, body) = lambda.split_first().unwrap(); let body = parse_tokv(body).boxed_local().await; diff --git a/orchid-std/src/macros/macro_lib.rs b/orchid-std/src/macros/macro_lib.rs index 5957d5c..a80e1eb 100644 --- a/orchid-std/src/macros/macro_lib.rs +++ b/orchid-std/src/macros/macro_lib.rs @@ -1,83 +1,79 @@ use orchid_base::sym; +use orchid_extension::TAtom; use orchid_extension::gen_expr::{call, new_atom}; use orchid_extension::tree::{GenMember, fun, prefix}; -use orchid_extension::{TAtom, exec}; use crate::macros::mactree::MacTree; -use crate::macros::resolve::resolve; +use crate::macros::resolve::{ArgStack, resolve}; use crate::macros::utils::{build_macro, mactree, mactreev}; use crate::{HomoTpl, UntypedTuple}; pub async fn gen_macro_lib() -> Vec { prefix("macros", [ - fun(true, "resolve", async |tpl: TAtom| resolve(tpl.own().await).await), + fun(true, "resolve", async |tpl: TAtom| { + resolve(tpl.own().await, ArgStack::end()).await + }), prefix("common", [ build_macro(None, ["..", "_", "="]).finish(), build_macro(Some(1), ["+"]) - .rule(mactreev!("...$" lhs 1 macros::common::+ "...$" rhs 0), [async |[lhs, rhs]| { - call(sym!(std::ops::add::resolve), (resolve(lhs).await, resolve(rhs).await)).await + .rule(mactreev!("...$" lhs 1 macros::common::+ "...$" rhs 0), [async |cx, [lhs, rhs]| { + call(sym!(std::ops::add::resolve), (cx.recur(lhs), cx.recur(rhs))).await }]) .finish(), build_macro(Some(1), ["-"]) - .rule(mactreev!("...$" lhs 1 macros::common::- "...$" rhs 0), [async |[lhs, rhs]| { - call(sym!(std::ops::sub::resolve), (resolve(lhs).await, resolve(rhs).await)).await + .rule(mactreev!("...$" lhs 1 macros::common::- "...$" rhs 0), [async |cx, [lhs, rhs]| { + call(sym!(std::ops::sub::resolve), (cx.recur(lhs), cx.recur(rhs))).await }]) .finish(), build_macro(Some(2), ["*"]) - .rule(mactreev!("...$" lhs 1 macros::common::* "...$" rhs 0), [async |[lhs, rhs]| { - call(sym!(std::ops::mul::resolve), (resolve(lhs).await, resolve(rhs).await)).await + .rule(mactreev!("...$" lhs 1 macros::common::* "...$" rhs 0), [async |cx, [lhs, rhs]| { + call(sym!(std::ops::mul::resolve), (cx.recur(lhs), cx.recur(rhs))).await }]) .finish(), build_macro(Some(2), ["/"]) - .rule(mactreev!("...$" lhs 1 macros::common::/ "...$" rhs 0), [async |[lhs, rhs]| { - call(sym!(std::ops::div::resolve), (resolve(lhs).await, resolve(rhs).await)).await + .rule(mactreev!("...$" lhs 1 macros::common::/ "...$" rhs 0), [async |cx, [lhs, rhs]| { + call(sym!(std::ops::div::resolve), (cx.recur(lhs), cx.recur(rhs))).await }]) .finish(), build_macro(Some(2), ["%"]) - .rule(mactreev!("...$" lhs 1 macros::common::% "...$" rhs 0), [async |[lhs, rhs]| { - call(sym!(std::ops::mod::resolve), (resolve(lhs).await, resolve(rhs).await)).await + .rule(mactreev!("...$" lhs 1 macros::common::% "...$" rhs 0), [async |cx, [lhs, rhs]| { + call(sym!(std::ops::mod::resolve), (cx.recur(lhs), cx.recur(rhs))).await }]) .finish(), build_macro(Some(3), ["."]) - .rule(mactreev!("...$" lhs 1 macros::common::. "...$" rhs 0), [async |[lhs, rhs]| { - call(sym!(std::ops::get::resolve), (resolve(lhs).await, resolve(rhs).await)).await + .rule(mactreev!("...$" lhs 1 macros::common::. "...$" rhs 0), [async |cx, [lhs, rhs]| { + call(sym!(std::ops::get::resolve), (cx.recur(lhs), cx.recur(rhs))).await }]) .finish(), build_macro(None, ["comma_list", ","]) .rule( mactreev!(macros::common::comma_list ( "...$" head 0 macros::common::, "...$" tail 1)), - [async |[head, tail]| { - exec(async |mut h| { - let recur = resolve(mactree!(macros::common::comma_list "push" tail ;)).await; - let mut tail = h.exec::>>(recur).await?; - tail.0.insert(0, h.exec(new_atom(head)).await?); - Ok(tail) - }) - .await + [async |mut cx, [head, tail]| { + let mut tail: HomoTpl> = + cx.exec(cx.recur(mactree!(macros::common::comma_list "push" tail ;))).await?; + tail.0.insert(0, cx.exec(new_atom(head)).await?); + Ok(tail) }], ) - .rule(mactreev!(macros::common::comma_list ( "...$" final_tail 0 )), [async |[tail]| { - HomoTpl(vec![new_atom(tail)]) - }]) - .rule(mactreev!(macros::common::comma_list()), [async |[]| UntypedTuple(Vec::new())]) + .rule(mactreev!(macros::common::comma_list ( "...$" final_tail 0 )), [ + async |_cx, [tail]| HomoTpl(vec![new_atom(tail)]), + ]) + .rule(mactreev!(macros::common::comma_list()), [async |_cx, []| UntypedTuple(Vec::new())]) .finish(), build_macro(None, ["semi_list", ";"]) .rule( mactreev!(macros::common::semi_list ( "...$" head 0 macros::common::; "...$" tail 1)), - [async |[head, tail]| { - exec(async |mut h| { - let recur = resolve(mactree!(macros::common::semi_list "push" tail ;)).await; - let mut tail = h.exec::>>(recur).await?; - tail.0.insert(0, h.exec(new_atom(head)).await?); - Ok(tail) - }) - .await + [async |mut cx, [head, tail]| { + let mut tail: HomoTpl> = + cx.exec(cx.recur(mactree!(macros::common::semi_list "push" tail ;))).await?; + tail.0.insert(0, cx.exec(new_atom(head)).await?); + Ok(tail) }], ) - .rule(mactreev!(macros::common::semi_list ( "...$" final_tail 0 )), [async |[tail]| { - HomoTpl(vec![new_atom(tail)]) - }]) - .rule(mactreev!(macros::common::semi_list()), [async |[]| UntypedTuple(Vec::new())]) + .rule(mactreev!(macros::common::semi_list ( "...$" final_tail 0 )), [ + async |_cx, [tail]| HomoTpl(vec![new_atom(tail)]), + ]) + .rule(mactreev!(macros::common::semi_list()), [async |_cx, []| UntypedTuple(Vec::new())]) .finish(), ]), ]) diff --git a/orchid-std/src/macros/macro_line.rs b/orchid-std/src/macros/macro_line.rs index 869282b..cc90ce5 100644 --- a/orchid-std/src/macros/macro_line.rs +++ b/orchid-std/src/macros/macro_line.rs @@ -8,10 +8,8 @@ use orchid_base::{ Comment, OrcRes, Paren, Parsed, Snippet, Token, clone, expect_end, expect_tok, is, line_items, mk_errv, report, sym, token_errv, try_pop_no_fluff, with_reporter, }; -use orchid_extension::TAtom; -use orchid_extension::{ToExpr, TryFromExpr}; use orchid_extension::gen_expr::{call, new_atom}; -use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser}; +use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser, TAtom, ToExpr, TryFromExpr}; use crate::macros::let_line::{dealias_mac_v, parse_tokv}; use crate::macros::macro_value::{Macro, MacroData, Rule}; diff --git a/orchid-std/src/macros/macro_system.rs b/orchid-std/src/macros/macro_system.rs index 9e5b216..7583f4b 100644 --- a/orchid-std/src/macros/macro_system.rs +++ b/orchid-std/src/macros/macro_system.rs @@ -13,6 +13,7 @@ use crate::macros::macro_value::Macro; use crate::macros::mactree_lexer::MacTreeLexer; use crate::macros::match_macros::{MatcherAtom, gen_match_macro_lib}; use crate::macros::ph_lexer::{PhAtom, PhLexer}; +use crate::macros::resolve::ArgStack; use crate::macros::stdlib::gen_std_macro_lib; use crate::macros::utils::MacroBodyArgCollector; use crate::{MacTree, StdSystem}; @@ -38,6 +39,7 @@ impl SystemCard for MacroSystem { Some(PhAtom::ops()), Some(MacroBodyArgCollector::ops()), Some(MatcherAtom::ops()), + Some(ArgStack::ops()), ] } } diff --git a/orchid-std/src/macros/macro_value.rs b/orchid-std/src/macros/macro_value.rs index 18337f4..e3814a7 100644 --- a/orchid-std/src/macros/macro_value.rs +++ b/orchid-std/src/macros/macro_value.rs @@ -2,10 +2,8 @@ use std::borrow::Cow; use std::rc::Rc; use never::Never; -use orchid_base::IStr; -use orchid_base::Sym; -use orchid_extension::Atomic; -use orchid_extension::{OwnedAtom, OwnedVariant}; +use orchid_base::{IStr, Sym}; +use orchid_extension::{Atomic, OwnedAtom, OwnedVariant}; use crate::macros::mactree::MacTreeSeq; use crate::macros::rule::matcher::Matcher; diff --git a/orchid-std/src/macros/mactree.rs b/orchid-std/src/macros/mactree.rs index a809967..5cd0ddb 100644 --- a/orchid-std/src/macros/mactree.rs +++ b/orchid-std/src/macros/mactree.rs @@ -9,9 +9,7 @@ use orchid_api_derive::Coding; use orchid_base::{ FmtCtx, FmtUnit, Format, IStr, OrcErrv, Paren, Pos, Sym, Variants, indent, tl_cache, }; -use orchid_extension::Atomic; -use orchid_extension::Expr; -use orchid_extension::{OwnedAtom, OwnedVariant}; +use orchid_extension::{Atomic, Expr, OwnedAtom, OwnedVariant}; fn union_rc_sets(seq: impl IntoIterator>>) -> Rc> { let mut acc = Rc::>::default(); @@ -172,9 +170,10 @@ impl MacTok { impl Format for MacTok { async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { match self { - Self::Value(v) => v.print(c).await, + Self::Value(v) => tl_cache!(Rc: Rc::new(Variants::default().bounded("$({0b})"))) + .units([v.print(c).await]), Self::Lambda(arg, b) => tl_cache!(Rc: Rc::new(Variants::default() - .unbounded("\\{0} {1l}") + // .unbounded("\\{0} {1l}") .bounded("(\\{0} {1b})"))) .units([arg.print(c).boxed_local().await, b.print(c).await]), Self::Name(n) => format!("{n}").into(), diff --git a/orchid-std/src/macros/mactree_lexer.rs b/orchid-std/src/macros/mactree_lexer.rs index 2402722..6ce4f66 100644 --- a/orchid-std/src/macros/mactree_lexer.rs +++ b/orchid-std/src/macros/mactree_lexer.rs @@ -2,12 +2,10 @@ use std::ops::RangeInclusive; use futures::FutureExt; use itertools::chain; -use orchid_base::Paren; -use orchid_base::{OrcRes, PARENS, is, mk_errv}; +use orchid_base::{OrcRes, PARENS, Paren, is, mk_errv}; use orchid_extension::gen_expr::new_atom; -use orchid_extension::{LexContext, Lexer, err_not_applicable}; -use orchid_extension::p_tree2gen; use orchid_extension::tree::{GenTok, GenTokTree, x_tok}; +use orchid_extension::{LexContext, Lexer, err_not_applicable, p_tree2gen}; use crate::macros::instantiate_tpl::InstantiateTplCall; use crate::macros::let_line::parse_tok; diff --git a/orchid-std/src/macros/match_macros.rs b/orchid-std/src/macros/match_macros.rs index fe0d4cb..ceb26b8 100644 --- a/orchid-std/src/macros/match_macros.rs +++ b/orchid-std/src/macros/match_macros.rs @@ -6,14 +6,12 @@ use futures::{Stream, StreamExt, stream}; use never::Never; use orchid_api_derive::Coding; use orchid_base::{OrcRes, Sym, fmt, is, mk_errv, sym}; -use orchid_extension::ToExpr; -use orchid_extension::{ExecHandle, exec}; -use orchid_extension::{Expr, ExprHandle}; -use orchid_extension::gen_expr::{GExpr, arg, bot, call, call_v, lam, new_atom}; +use orchid_extension::gen_expr::{bot, call, call_v, lam, new_atom}; use orchid_extension::tree::{GenMember, fun, prefix}; -use orchid_extension::{Atomic, OwnedAtom, OwnedVariant, TAtom}; +use orchid_extension::{ + Atomic, ExecHandle, Expr, ExprHandle, OwnedAtom, OwnedVariant, TAtom, ToExpr, exec, +}; -use crate::macros::resolve::resolve; use crate::macros::utils::{build_macro, mactree, mactreev}; use crate::std::reflection::sym_atom::SymAtom; use crate::{HomoTpl, MacTok, MacTree, OrcOpt, Tpl, UntypedTuple, api}; @@ -83,78 +81,63 @@ pub async fn gen_match_macro_lib() -> Vec { }), build_macro(None, ["match", "match_rule", "_row", "=>"]) .rule(mactreev!("pattern::match" "...$" value 0 { "..$" rules 0 }), [ - async |[value, rules]| { - exec(async move |mut h| { - let rule_lines = h - .exec::>>(call( - sym!(macros::resolve), - new_atom(mactree!(macros::common::semi_list "push" rules.clone();)), - )) - .await?; - let mut rule_atoms = Vec::<(TAtom, Expr)>::new(); - for line_mac in rule_lines.0.iter() { - let Tpl((matcher, body)) = h - .exec(call( - sym!(macros::resolve), - new_atom(mactree!(pattern::_row "push" line_mac.own().await ;)), - )) - .await?; - rule_atoms.push((matcher, body)); - } - let base_case = lam::<0>(bot(mk_errv( + async |mut cx, [value, rules]| { + let rule_lines: HomoTpl> = + cx.exec(cx.recur(mactree!(macros::common::semi_list "push" rules.clone();))).await?; + let mut rule_atoms = Vec::<(TAtom, Expr)>::new(); + for line_mac in rule_lines.0.iter() { + let Tpl((matcher, body)) = + cx.exec(cx.recur(mactree!(pattern::_row "push" line_mac.own().await ;))).await?; + rule_atoms.push((matcher, body)); + } + let base_case = lam(async |_| { + bot(mk_errv( is("No branches match").await, "None of the patterns matches this value", [rules.pos()], - ))) - .await; - let match_expr = stream::iter(rule_atoms.into_iter().rev()) - .fold(base_case, async |tail, (mat, body)| { - lam::<0>(call(sym!(pattern::match_one), (mat, arg(0), body, call(tail, arg(0))))) - .await - }) - .await; - Ok(call(match_expr, resolve(value).await)) + )) }) - .await + .await; + let match_expr = stream::iter(rule_atoms.into_iter().rev()) + .fold(base_case, |tail, (mat, body)| { + lam(async move |x| { + call(sym!(pattern::match_one), (mat, x, body, call(tail, x))).await + }) + }) + .await; + Ok(call(match_expr, cx.recur(value))) }, ]) - .rule(mactreev!(pattern::match_rule (( "...$" pattern 0 ))), [async |[pattern]| { - resolve(mactree!(pattern::match_rule "push" pattern; )).await + .rule(mactreev!(pattern::match_rule (( "...$" pattern 0 ))), [async |cx, [pattern]| { + cx.recur(mactree!(pattern::match_rule "push" pattern; )).to_gen().await }]) - .rule(mactreev!(pattern::match_rule ( macros::common::_ )), [async |[]| { + .rule(mactreev!(pattern::match_rule ( macros::common::_ )), [async |_cx, []| { Ok(new_atom(MatcherAtom { keys: Vec::new(), - matcher: lam::<0>(OrcOpt(Some(Tpl(())))).await.create().await, + matcher: lam(async |_| OrcOpt(Some(Tpl(()))).to_gen().await).await.create().await, })) }]) .rule(mactreev!(pattern::_row ( "...$" pattern 0 pattern::=> "...$" value 1 )), [ - async |[pattern, mut value]| { - exec(async move |mut h| -> OrcRes, GExpr)>> { - let Ok(pat) = h - .exec::>(call( - sym!(macros::resolve), - new_atom(mactree!(pattern::match_rule "push" pattern.clone();)), - )) - .await - else { - return Err(mk_errv( - is("Invalid pattern").await, - format!("Could not parse {} as a match pattern", fmt(&pattern).await), - [pattern.pos()], - )); - }; - value = (pat.keys()) - .fold(value, async |value, name| mactree!("l_" name; ( "push" value ; ))) - .await; - Ok(Tpl((pat, resolve(value).await))) - }) - .await + async |mut cx, [pattern, mut value]| -> OrcRes, _)>> { + let Ok(pat): OrcRes> = + cx.exec(cx.recur(mactree!(pattern::match_rule "push" pattern.clone();))).await + else { + return Err(mk_errv( + is("Invalid pattern").await, + format!("Could not parse {} as a match pattern", fmt(&pattern).await), + [pattern.pos()], + )); + }; + value = (pat.keys()) + .fold(value, async |value, name| mactree!("l_" name; ( "push" value ; ))) + .await; + Ok(Tpl((pat, cx.recur(value)))) }, ]) .finish(), fun(true, "ref_body", async |val| OrcOpt(Some(UntypedTuple(vec![val])))), build_macro(None, ["ref"]) - .rule(mactreev!(pattern::match_rule(pattern::ref "$" name)), [async |[name]| { + .rule(mactreev!(pattern::match_rule(pattern::ref "$" name)), [async |_cx, [name]| { let MacTok::Name(name) = name.tok() else { return Err(mk_errv( is("pattern 'ref' requires a name to bind to").await, diff --git a/orchid-std/src/macros/mod.rs b/orchid-std/src/macros/mod.rs index dbe97a3..afe6982 100644 --- a/orchid-std/src/macros/mod.rs +++ b/orchid-std/src/macros/mod.rs @@ -8,6 +8,7 @@ pub mod mactree; mod mactree_lexer; pub mod match_macros; mod ph_lexer; +pub mod postmac; mod resolve; mod rule; pub mod stdlib; diff --git a/orchid-std/src/macros/ph_lexer.rs b/orchid-std/src/macros/ph_lexer.rs index 6306e0e..9343ff1 100644 --- a/orchid-std/src/macros/ph_lexer.rs +++ b/orchid-std/src/macros/ph_lexer.rs @@ -1,10 +1,8 @@ use orchid_api_derive::Coding; use orchid_base::{FmtUnit, OrcRes, es, is, mk_errv, name_char, name_start}; -use orchid_extension::Atomic; use orchid_extension::gen_expr::new_atom; -use orchid_extension::{LexContext, Lexer, err_not_applicable}; use orchid_extension::tree::{GenTokTree, x_tok}; -use orchid_extension::{ThinAtom, ThinVariant}; +use orchid_extension::{Atomic, LexContext, Lexer, ThinAtom, ThinVariant, err_not_applicable}; use crate::macros::mactree::{Ph, PhKind}; diff --git a/orchid-std/src/macros/postmac.rs b/orchid-std/src/macros/postmac.rs new file mode 100644 index 0000000..02dfc55 --- /dev/null +++ b/orchid-std/src/macros/postmac.rs @@ -0,0 +1,53 @@ +use std::any::TypeId; +use std::borrow::Cow; +use std::marker::PhantomData; + +use never::Never; +use orchid_extension::gen_expr::GExpr; +use orchid_extension::{ + Atomic, ClonableToExprDyn, MethodSetBuilder, OwnedAtom, OwnedVariant, ToExpr, ToExprFuture, +}; + +fn assert_not_gexpr() { + assert_ne!( + TypeId::of::(), + TypeId::of::(), + "Cannot output GExpr, use PostMac::boxed or PostMac::boxed_clone" + ); +} + +struct PostMacCx<'a>(PhantomData<&'a ()>); +impl PostMacCx<'_> { + pub fn ex(&self, val: PostMac) -> T { val.0 } +} + +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct PostMac(T); +impl PostMac { + pub fn new(t: T) -> Self { + assert_not_gexpr::(); + Self(t) + } +} +impl + Clone + 'static> PostMac> { + pub fn with(f: impl for<'a> FnOnce(PostMacCx<'a>) -> T + Clone + 'static) -> Self { + assert_not_gexpr::(); + PostMac(ToExprFuture(f(PostMacCx(PhantomData)))) + } +} +impl PostMac { + pub fn atom(self) -> PostMacAtom { PostMac(Box::new(self.0)) } +} +impl From for PostMac { + fn from(value: T) -> Self { Self(value) } +} +pub type PostMacAtom = PostMac>; +impl Atomic for PostMacAtom { + type Data = (); + type Variant = OwnedVariant; + fn reg_methods() -> MethodSetBuilder { MethodSetBuilder::new() } +} +impl OwnedAtom for PostMacAtom { + type Refs = Never; + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } +} diff --git a/orchid-std/src/macros/resolve.rs b/orchid-std/src/macros/resolve.rs index 0847778..ab654ef 100644 --- a/orchid-std/src/macros/resolve.rs +++ b/orchid-std/src/macros/resolve.rs @@ -1,71 +1,146 @@ +use std::borrow::Cow; use std::collections::VecDeque; use std::ops::{Add, Range}; +use std::rc::Rc; use async_fn_stream::stream; use futures::{FutureExt, StreamExt, stream}; use hashbrown::{HashMap, HashSet}; use itertools::Itertools; +use never::Never; use orchid_base::{NameLike, Paren, Pos, Sym, VPath, fmt, is, log, mk_errv}; -use orchid_extension::gen_expr::{GExpr, arg, bot, call, call_v, dyn_lambda, new_atom}; -use orchid_extension::{ReflMemKind, TAtom, ToExpr, exec, refl}; +use orchid_extension::gen_expr::{GExpr, GExprKind, bot, call, call_v, new_atom}; +use orchid_extension::{ + Atomic, ExecHandle, OwnedAtom, OwnedVariant, ReflMemKind, TAtom, ToExpr, ToExprFuture, exec, refl, +}; use subslice_offset::SubsliceOffset; -use substack::Substack; use crate::macros::macro_value::{Macro, Rule}; use crate::macros::mactree::MacTreeSeq; +use crate::macros::postmac::{PostMac, PostMacAtom}; use crate::macros::rule::state::{MatchState, StateEntry}; use crate::{MacTok, MacTree}; -pub async fn resolve(val: MacTree) -> GExpr { - exec(async move |mut h| { - writeln!(log("debug"), "Macro-resolving {}", fmt(&val).await).await; - let root = refl(); - let mut macros = HashMap::new(); - for n in val.glossary() { - let (foot, body) = n.split_last_seg(); - let new_name = VPath::new(body.iter().cloned()) - .name_with_suffix(is(&format!("__macro__{foot}")).await) - .to_sym() - .await; - if let Ok(ReflMemKind::Const) = root.get_by_path(&new_name).await.map(|m| m.kind()) { - let Ok(mac) = h.exec::>(new_name).await else { continue }; - let mac = mac.own().await; - macros.entry(mac.0.canonical_name.clone()).or_insert(mac); +pub enum ArgStackKind { + End, + Cons(Sym, ArgStack), +} +#[derive(Clone)] +pub struct ArgStack { + kind: Rc, + len: usize, +} +impl ArgStack { + pub fn end() -> Self { ArgStack { kind: Rc::new(ArgStackKind::End), len: 0 } } +} +impl Default for ArgStack { + fn default() -> Self { Self::end() } +} +impl Atomic for ArgStack { + type Data = (); + type Variant = OwnedVariant; +} +impl OwnedAtom for ArgStack { + type Refs = Never; + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } +} + +/// # TODO +/// +/// convert macro system to return MacTree or otherwise bring it up to +/// speed with the new [ToExpr] / [GExpr] division +/// +/// Idea: MacTree needs to be passed wherever the meaning of an expression can +/// change depending on where in the tree it is bound +/// +/// Idea: lowering MacTree to ToExpr implementors is possible by just knowing +/// what names are bound, not their values, but lowering it to GExpr is not. +/// +/// Problem: The required information is stackbound, so recursive macro matching +/// needs to be a single coroutine. Even when it forks out to Orchid, recursive +/// calls need to point back to this coroutine. Being a coroutine, this +/// recursion can overflow the Rust stack. +/// +/// Limits: +/// +/// - The concrete MacTree being generated sometimes depends on recursive macro +/// calls which need to complete before we return a MacTree +/// - Any placeholders representing expressions must be recursed over before +/// returning in a MacTree +/// - Exactly one of these things must be done on a subtree +/// +/// Takeaways: +/// +/// - Resolution should not lower to GExpr +/// - Consider separate types MacTree vs resolved tree +/// - MacTree can be built for the purpose of passing into recur +/// - Resolved tree can be built for the purpose of returning +/// - cannot contain [...], {...}, (), ( ... \. ) +/// - is pretty much GExpr with sym / dynamic arg binding instead of +/// numbered. Can this be a wrapper type over ToExpr instead? +/// - In order to move recursive state off the stack, we need a loophole +/// for lambdas +/// - Ensures that resolution only happens exactly once which is important +/// because double resolve can produce bugs that are difficult to catch +/// - Macros may return ResolvedTree but they can also return a datastructure +/// containing MacTree +/// - Macros may never lower ResolvedTree to GExpr directly because it may +/// refer to bound arguments by name +/// - Macros returning datastructures can only ever be called as logic while +/// those returning ResolvedTree can only ever be inlined +/// - this is a type system concern so addressing it here is unnecessary +/// +/// Problems: +/// - ToExpr are not usually copiable by default +/// - plain-orchid macros should be able to annotate data-to-return and +/// data-to-resolve with the same tick symbol to limit conceptual complexity, +/// - the case where a macro deliberately wants to bind a name explicitly within +/// a subexpression is tricky +/// +/// The best option probably remains for resolve to process and return MacTree, +/// and for there to be a separate "lower" function. Nothing as yet suggests +/// however that macros can't be allowed to return different types +pub async fn resolve(h: &mut ExecHandle<'_>, val: MacTree, arg_stk: ArgStack) -> PostMacAtom { + writeln!(log("debug"), "Macro-resolving {}", fmt(&val).await).await; + let root = refl(); + let mut macros = HashMap::new(); + for n in val.glossary() { + let (foot, body) = n.split_last_seg(); + let new_name = VPath::new(body.iter().cloned()) + .name_with_suffix(is(&format!("__macro__{foot}")).await) + .to_sym() + .await; + if let Ok(ReflMemKind::Const) = root.get_by_path(&new_name).await.map(|m| m.kind()) { + let Ok(mac) = h.exec::>(new_name).await else { continue }; + let mac = mac.own().await; + macros.entry(mac.0.canonical_name.clone()).or_insert(mac); + } + } + let mut exclusive = Vec::new(); + let mut prios = Vec::::new(); + let mut priod = Vec::::new(); + for (_, mac) in macros.iter() { + let mut record = FilteredMacroRecord { mac, rules: Vec::new() }; + for (rule_i, rule) in mac.0.rules.iter().enumerate() { + if rule.pattern.glossary.is_subset(val.glossary()) { + record.rules.push(rule_i); } } - let mut exclusive = Vec::new(); - let mut prios = Vec::::new(); - let mut priod = Vec::::new(); - for (_, mac) in macros.iter() { - let mut record = FilteredMacroRecord { mac, rules: Vec::new() }; - for (rule_i, rule) in mac.0.rules.iter().enumerate() { - if rule.pattern.glossary.is_subset(val.glossary()) { - record.rules.push(rule_i); - } - } - if !record.rules.is_empty() { - match mac.0.prio { - None => exclusive.push(record), - Some(prio) => { - let i = prios.partition_point(|p| *p > prio); - prios.insert(i, prio); - priod.insert(i, record); - }, - } + if !record.rules.is_empty() { + match mac.0.prio { + None => exclusive.push(record), + Some(prio) => { + let i = prios.partition_point(|p| *p > prio); + prios.insert(i, prio); + priod.insert(i, record); + }, } } - let mut rctx = ResolveCtx { exclusive, priod }; - let gex = resolve_one(&mut rctx, Substack::Bottom, &val).await; - writeln!( - log("debug"), - "Macro-resolution over {}\nreturned {}", - fmt(&val).await, - fmt(&gex).await - ) - .await; - gex - }) - .await + } + let mut rctx = ResolveCtx { exclusive, priod }; + let gex = resolve_one(&mut rctx, arg_stk, &val).await; + writeln!(log("debug"), "Macro-resolution over {}", fmt(&val).await).await; + gex } /// Rules belonging to one macro that passed a particular filter @@ -83,37 +158,54 @@ struct ResolveCtx<'a> { pub priod: Vec>, } -async fn resolve_one( - ctx: &mut ResolveCtx<'_>, - arg_stk: Substack<'_, Sym>, - value: &MacTree, -) -> GExpr { +async fn resolve_one(ctx: &mut ResolveCtx<'_>, arg_stk: ArgStack, value: &MacTree) -> PostMacAtom { + eprintln!("Resolving unit {}", fmt(value).await); match value.tok() { MacTok::Ph(_) | MacTok::Slot => panic!("Forbidden element in value mactree"), - MacTok::Bottom(err) => bot(err.clone()), - MacTok::Value(v) => v.clone().to_gen().await, - MacTok::Name(n) => match arg_stk.iter().position(|arg| arg == n) { - Some(de_bruijn) => arg((arg_stk.len() - 1 - de_bruijn).try_into().unwrap()), - None => n.clone().to_gen().await, + MacTok::Bottom(err) => PostMac::new(bot(err.clone())).atom(), + MacTok::Value(v) => { + eprintln!("Found value {}", fmt(v).await); + PostMac::new(v.clone()).atom() + }, + MacTok::Name(n) => { + eprintln!("Looking for {n} among ["); + let mut cur = &arg_stk; + let mut counter = 0; + while let ArgStackKind::Cons(name, next) = &*cur.kind { + cur = next; + counter += 1; + eprintln!("{name}, "); + if name == n { + return PostMac::new(GExprKind::Arg(counter).at(value.pos())).atom(); + } + } + PostMac::new(n.clone()).atom() }, MacTok::Lambda(arg, body) => { + eprintln!("Found lambda \\{} {}", fmt(arg).await, fmt(body).await); let MacTok::Name(name) = &*arg.tok else { - return bot(mk_errv( + return PostMac::new(bot(mk_errv( is("Syntax error after macros").await, "This token ends up as a binding, consider replacing it with a name", [arg.pos()], - )); + ))) + .atom(); }; - let arg_pos = arg_stk.len() as u64; - let arg_stk = arg_stk.push(name.clone()); - dyn_lambda(arg_pos, resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await).await + let arg_stk = + ArgStack { len: arg_stk.len + 1, kind: Rc::new(ArgStackKind::Cons(name.clone(), arg_stk)) }; + let body = resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await; + let body2 = body.clone(); + let pos = value.pos(); + PostMac::with(async |cx| GExprKind::Lambda(Box::new(cx.ex(body).to_gen().await)).at(pos)) + .atom() }, MacTok::S(Paren::Round, body) => resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await, - MacTok::S(..) => bot(mk_errv( + MacTok::S(..) => PostMac::new(bot(mk_errv( is("Leftover [] or {} not matched by macro").await, format!("{} was not matched by any macro", fmt(value).await), [value.pos()], - )), + ))) + .atom(), } } @@ -133,17 +225,18 @@ fn subsection( async fn resolve_seq( ctx: &mut ResolveCtx<'_>, - arg_stk: Substack<'_, Sym>, + arg_stk: ArgStack, val: MacTreeSeq, fallback_pos: Pos, -) -> GExpr { +) -> PostMacAtom { if val.items.is_empty() { - return bot(mk_errv( + return PostMac::new(bot(mk_errv( is("Empty sequence").await, "() or (\\arg ) left after macro execution. \ This is usually caused by an incomplete call to a macro with bad error detection", [fallback_pos], - )); + ))) + .atom(); } // A sorted collection of overlapping but non-nested matches to exclusive // macros @@ -226,14 +319,15 @@ async fn resolve_seq( }) .reduce(|l, r| l + r); if let Some(error) = error { - return bot(error); + return PostMac::new(bot(error)).atom(); } // no conflicts, apply all exclusive matches for (range, mac, rule, state) in x_matches.into_iter().rev() { // backwards so that the non-overlapping ranges remain valid let pos = (state.names().flat_map(|r| r.1).cloned().reduce(Pos::add)) .expect("All macro rules must contain at least one locally defined name"); - let subex = mk_body_call(mac, rule, &state, pos.clone()).await.to_expr().await; + let subex = + mk_body_call(mac, rule, &state, pos.clone(), arg_stk.clone()).await.to_expr().await; new_val.splice(range, [MacTok::Value(subex).at(pos)]); } }; @@ -251,7 +345,8 @@ async fn resolve_seq( let range = pre.len()..new_val.len() - suf.len(); let pos = (state.names().flat_map(|pair| pair.1).cloned().reduce(Pos::add)) .expect("All macro rules must contain at least one locally defined name"); - let subex = mk_body_call(mac, rule, &state, pos.clone()).await.to_expr().await; + let subex = + mk_body_call(mac, rule, &state, pos.clone(), arg_stk.clone()).await.to_expr().await; std::mem::drop(state); new_val.splice(range, [MacTok::Value(subex).at(pos)]); } @@ -267,11 +362,21 @@ async fn resolve_seq( let first = exprs.pop_front().expect( "We checked first that it isn't empty, and named macros get replaced with their results", ); - stream::iter(exprs).fold(first, async |f, x| call(f, x).await).await + PostMac::with(async move |cx| { + stream::iter(exprs).fold(cx.ex(first), async |f, x| call(f, cx.ex(x)).await).await + }) + .await + .atom() } -async fn mk_body_call(mac: &Macro, rule: &Rule, state: &MatchState<'_>, pos: Pos) -> GExpr { - let mut call_args = vec![]; +async fn mk_body_call( + mac: &Macro, + rule: &Rule, + state: &MatchState<'_>, + pos: Pos, + arg_stk: ArgStack, +) -> GExpr { + let mut call_args = vec![new_atom(arg_stk).at(Pos::None)]; for name in rule.ph_names.iter() { call_args.push(match state.get(name).expect("Missing state entry for placeholder") { StateEntry::Scalar(scal) => new_atom((**scal).clone()), diff --git a/orchid-std/src/macros/rule/matcher.rs b/orchid-std/src/macros/rule/matcher.rs index 01d59db..c4dba57 100644 --- a/orchid-std/src/macros/rule/matcher.rs +++ b/orchid-std/src/macros/rule/matcher.rs @@ -1,8 +1,7 @@ use std::fmt; use std::rc::Rc; -use orchid_base::Sym; -use orchid_base::{OrcRes, is}; +use orchid_base::{OrcRes, Sym, is}; use super::any_match::any_match; use super::build::mk_any; diff --git a/orchid-std/src/macros/rule/shared.rs b/orchid-std/src/macros/rule/shared.rs index bf4e223..e7c39b5 100644 --- a/orchid-std/src/macros/rule/shared.rs +++ b/orchid-std/src/macros/rule/shared.rs @@ -3,8 +3,7 @@ use std::fmt; use itertools::Itertools; -use orchid_base::{PARENS, Paren}; -use orchid_base::{IStr, Side, Sym}; +use orchid_base::{IStr, PARENS, Paren, Side, Sym}; pub enum ScalMatcher { Name(Sym), diff --git a/orchid-std/src/macros/rule/state.rs b/orchid-std/src/macros/rule/state.rs index 8ef65f8..22a6092 100644 --- a/orchid-std/src/macros/rule/state.rs +++ b/orchid-std/src/macros/rule/state.rs @@ -2,9 +2,7 @@ use std::any::Any; use hashbrown::HashMap; -use orchid_base::Pos; -use orchid_base::Sym; -use orchid_base::{IStr, join_maps, match_mapping}; +use orchid_base::{IStr, Pos, Sym, join_maps, match_mapping}; use crate::macros::MacTree; diff --git a/orchid-std/src/macros/stdlib/funnctional.rs b/orchid-std/src/macros/stdlib/funnctional.rs index 03931df..71eaadd 100644 --- a/orchid-std/src/macros/stdlib/funnctional.rs +++ b/orchid-std/src/macros/stdlib/funnctional.rs @@ -1,12 +1,11 @@ use orchid_extension::tree::{GenMember, prefix}; -use crate::macros::resolve::resolve; use crate::macros::utils::{build_macro, mactree, mactreev}; pub async fn gen_functional_macro_lib() -> Vec { prefix("std::fn", [build_macro(Some(4), ["|>"]) - .rule(mactreev!("...$" lhs 0 "std::fn::|>" "$" fun "...$" rhs 0), [async |[lhs, fun, rhs]| { - resolve(mactree!(("push" fun ; "push" lhs ;) "pushv" rhs ;)).await - }]) + .rule(mactreev!("...$" lhs 0 "std::fn::|>" "$" fun "...$" rhs 0), [ + async |cx, [lhs, fun, rhs]| cx.recur(mactree!(("push" fun ; "push" lhs ;) "pushv" rhs ;)), + ]) .finish()]) } diff --git a/orchid-std/src/macros/stdlib/option.rs b/orchid-std/src/macros/stdlib/option.rs index 0d12223..a06a104 100644 --- a/orchid-std/src/macros/stdlib/option.rs +++ b/orchid-std/src/macros/stdlib/option.rs @@ -5,7 +5,6 @@ use orchid_extension::tree::{GenMember, fun, prefix}; use orchid_extension::{Expr, TAtom, ToExpr, exec}; use crate::macros::match_macros::MatcherAtom; -use crate::macros::resolve::resolve; use crate::macros::utils::{build_macro, mactree, mactreev}; use crate::{OrcOpt, Tpl}; @@ -26,19 +25,16 @@ pub async fn gen_option_macro_lib() -> Vec { ), build_macro(None, ["some", "none"]) .rule(mactreev!(pattern::match_rule ( std::option::some "...$" sub_pattern 0)), [ - |[sub]: [_; _]| { - exec(async move |mut h| { - let sub = h - .exec::>(resolve(mactree!(pattern::match_rule "push" sub;)).await) - .await?; - Ok(new_atom(MatcherAtom { - keys: sub.keys().collect().await, - matcher: call(sym!(std::option::is_some_body), sub).await.create().await, - })) - }) + async |mut cx, [sub]| { + let sub: TAtom = + cx.exec(cx.recur(mactree!(pattern::match_rule "push" sub;))).await?; + Ok(new_atom(MatcherAtom { + keys: sub.keys().collect().await, + matcher: call(sym!(std::option::is_some_body), sub).await.create().await, + })) }, ]) - .rule(mactreev!(pattern::match_rule(std::option::none)), [async |[]: [_; _]| { + .rule(mactreev!(pattern::match_rule(std::option::none)), [async |_cx, []| { new_atom(MatcherAtom { keys: vec![], matcher: sym!(std::option::is_none_body).to_expr().await, diff --git a/orchid-std/src/macros/stdlib/record.rs b/orchid-std/src/macros/stdlib/record.rs index cd27344..36abbb8 100644 --- a/orchid-std/src/macros/stdlib/record.rs +++ b/orchid-std/src/macros/stdlib/record.rs @@ -1,41 +1,27 @@ use orchid_base::sym; -use orchid_extension::TAtom; -use orchid_extension::ToExpr; -use orchid_extension::exec; -use orchid_extension::Expr; -use orchid_extension::gen_expr::{call, new_atom}; +use orchid_extension::gen_expr::call; use orchid_extension::tree::{GenMember, prefix}; +use orchid_extension::{Expr, TAtom, ToExpr}; -use crate::macros::resolve::resolve; use crate::macros::utils::{build_macro, mactree, mactreev}; use crate::std::string::str_atom::IntStrAtom; use crate::{HomoTpl, MacTree, Tpl}; pub async fn gen_record_macro_lib() -> Vec { prefix("std::record", [build_macro(None, ["r", "_row"]) - .rule(mactreev!(std::record::r[ "...$" elements 0 ]), [async |[elements]: [_; _]| { - exec(async move |mut h| { - let tup = h - .exec::>>(call( - sym!(macros::resolve), - new_atom(mactree!((macros::common::comma_list "push" elements ;))), - )) - .await?; - let mut record = sym!(std::record::empty).to_gen().await; - for item_exprh in tup.0 { - let Tpl((key, value)) = h - .exec::, Expr)>>( - resolve(mactree!(std::record::_row "push" item_exprh.own().await ;)).await, - ) - .await?; - record = call(sym!(std::record::set), (record, key, value)).await; - } - Ok(record) - }) - .await + .rule(mactreev!(std::record::r[ "...$" elements 0 ]), [async |mut cx, [elements]| { + let tup: HomoTpl> = + cx.exec(cx.recur(mactree!((macros::common::comma_list "push" elements ;)))).await?; + let mut record = sym!(std::record::empty).to_gen().await; + for item_exprh in tup.0 { + let Tpl((key, value)): Tpl<(TAtom, Expr)> = + cx.exec(cx.recur(mactree!(std::record::_row "push" item_exprh.own().await ;))).await?; + record = call(sym!(std::record::set), (record, key, value)).await; + } + Ok(record) }]) - .rule(mactreev!(std::record::_row ( "$" name "...$" value 1 )), [async |[name, value]| { - Ok(Tpl((resolve(name).await, resolve(value).await))) + .rule(mactreev!(std::record::_row ( "$" name "...$" value 1 )), [async |cx, [name, value]| { + Ok(Tpl((cx.recur(name), cx.recur(value)))) }]) .finish()]) } diff --git a/orchid-std/src/macros/stdlib/tuple.rs b/orchid-std/src/macros/stdlib/tuple.rs index 297fa7b..3760fc9 100644 --- a/orchid-std/src/macros/stdlib/tuple.rs +++ b/orchid-std/src/macros/stdlib/tuple.rs @@ -1,24 +1,20 @@ use futures::{StreamExt, stream}; use orchid_base::{OrcRes, sym}; -use orchid_extension::TAtom; -use orchid_extension::ToExpr; -use orchid_extension::exec; -use orchid_extension::Expr; use orchid_extension::gen_expr::{GExpr, call, new_atom}; use orchid_extension::tree::{GenMember, fun, prefix}; +use orchid_extension::{Expr, TAtom, ToExpr, exec}; use crate::macros::match_macros::MatcherAtom; -use crate::macros::utils::{build_macro, mactree, mactreev}; +use crate::macros::utils::{RuleCtx, build_macro, mactree, mactreev}; use crate::{HomoTpl, MacTree, OrcOpt}; pub async fn gen_tuple_macro_lib() -> Vec { prefix("std::tuple", [ build_macro(None, ["t"]) - .rule(mactreev!(std::tuple::t [ "...$" elements 0 ]), [|[elements]: [_; _]| { - exec(async move |mut h| { - let tup = h - .exec::>>(call(sym!(macros::resolve), - new_atom(mactree!((macros::common::comma_list "push" elements ;))), + .rule(mactreev!(std::tuple::t [ "...$" elements 0 ]), [async |mut cx, [elements]| { + let tup: HomoTpl> = cx + .exec(cx.recur( + mactree!((macros::common::comma_list "push" elements ;)), )) .await?; let val = stream::iter(&tup.0[..]) @@ -33,65 +29,52 @@ pub async fn gen_tuple_macro_lib() -> Vec { }) .await; Ok(val) - }) }]) .rule( mactreev!(pattern::match_rule(std::tuple::t[ "...$" elements 0 macros::common::, macros::common::..])), - [async |[elements]: [_; _]| parse_tpl(elements, Some(mactree!(macros::common::_))).await], + [async |cx, [elements]| parse_tpl(cx, elements, Some(mactree!(macros::common::_))).await], ) .rule( mactreev!(pattern::match_rule( std::tuple::t[ "...$" elements 1 macros::common::, macros::common::.. "...$" tail 0] )), - [async |[elements, tail]: [_; _]| parse_tpl(elements, Some(tail)).await], + [async |cx, [elements, tail]| parse_tpl(cx, elements, Some(tail)).await], ) .rule(mactreev!(pattern::match_rule(std::tuple::t[ "...$" elements 0])), [ - |[elements]: [_; _]| parse_tpl(elements, None), + async |cx, [elements]| parse_tpl(cx, elements, None).await, ]) .finish(), fun(false, "matcher_body", tuple_matcher_body), ]) } -fn parse_tpl(elements: MacTree, tail_matcher: Option) -> impl Future { - exec(async move |mut h| -> OrcRes { - let tup = h - .exec::>>(call(sym!(macros::resolve), new_atom( - mactree!((macros::common::comma_list "push" elements ;)), - ))) - .await?; - let mut subs = Vec::with_capacity(tup.0.len()); - for mac_a in &tup.0[..] { - let sub = h - .exec::>(call(sym!(macros::resolve), new_atom( - mactree!(pattern::match_rule ("push" mac_a.own().await ;)), - ))) - .await?; - subs.push(sub); - } - let tail_matcher = match tail_matcher { - Some(mac) => Some( - h.exec::>(call(sym!(macros::resolve), new_atom( - mactree!(pattern::match_rule "push" mac ;), - ))) - .await?, - ), - None => None, - }; - Ok(new_atom(MatcherAtom { - keys: stream::iter(&subs[..]) - .flat_map(|t| t.keys()) - .chain(stream::iter(&tail_matcher).flat_map(|mat| mat.keys())) - .collect() - .await, - matcher: call(sym!(std::tuple::matcher_body), ( - HomoTpl(subs), - OrcOpt(tail_matcher), - )) +async fn parse_tpl( + mut cx: RuleCtx<'_>, + elements: MacTree, + tail_matcher: Option, +) -> OrcRes { + let tup: HomoTpl> = + cx.exec(cx.recur(mactree!((macros::common::comma_list "push" elements ;)))).await?; + let mut subs = Vec::with_capacity(tup.0.len()); + for mac_a in &tup.0[..] { + let sub: TAtom = + cx.exec(cx.recur(mactree!(pattern::match_rule ("push" mac_a.own().await ;)))).await?; + subs.push(sub); + } + let tail_matcher: Option> = match tail_matcher { + Some(mac) => Some(cx.exec(cx.recur(mactree!(pattern::match_rule "push" mac ;))).await?), + None => None, + }; + Ok(new_atom(MatcherAtom { + keys: stream::iter(&subs[..]) + .flat_map(|t| t.keys()) + .chain(stream::iter(&tail_matcher).flat_map(|mat| mat.keys())) + .collect() + .await, + matcher: call(sym!(std::tuple::matcher_body), (HomoTpl(subs), OrcOpt(tail_matcher))) .to_expr() .await, - })) - }) + })) } fn tuple_matcher_body( diff --git a/orchid-std/src/macros/utils.rs b/orchid-std/src/macros/utils.rs index 27dbd0a..6111c80 100644 --- a/orchid-std/src/macros/utils.rs +++ b/orchid-std/src/macros/utils.rs @@ -6,14 +6,16 @@ use futures::StreamExt; use futures::future::LocalBoxFuture; use itertools::{Itertools, chain}; use never::Never; -use orchid_base::{NameLike, Sym, VPath, is}; -use orchid_extension::ToExpr; +use orchid_base::{NameLike, OrcRes, Sym, VPath, is}; use orchid_extension::gen_expr::{GExpr, new_atom}; use orchid_extension::tree::{GenMember, MemKind, cnst, lazy}; -use orchid_extension::{Atomic, OwnedAtom, OwnedVariant, TAtom}; +use orchid_extension::{ + Atomic, ExecHandle, OwnedAtom, OwnedVariant, TAtom, ToExpr, ToExprFuture, TryFromExpr, exec, +}; use crate::macros::macro_value::{Macro, MacroData, Rule}; use crate::macros::mactree::MacTreeSeq; +use crate::macros::resolve::{ArgStack, resolve}; use crate::macros::rule::matcher::Matcher; use crate::{MacTok, MacTree}; @@ -22,8 +24,9 @@ pub type Args = Vec; #[derive(Clone)] pub struct MacroBodyArgCollector { argc: usize, + arg_stack: Option, args: Args, - cb: Rc LocalBoxFuture<'static, GExpr>>, + cb: Rc Fn(RuleCtx<'a>, Args) -> LocalBoxFuture<'a, GExpr>>, } impl Atomic for MacroBodyArgCollector { type Data = (); @@ -39,12 +42,23 @@ impl OwnedAtom for MacroBodyArgCollector { self.clone().call(arg).await } async fn call(mut self, arg: orchid_extension::Expr) -> GExpr { - let atom = (TAtom::::downcast(arg.handle()).await).unwrap_or_else(|_| { - panic!("This is an intermediary value, the argument types are known in advance") - }); - self.args.push(atom.own().await); + if self.arg_stack.is_none() { + let atom = (TAtom::::downcast(arg.handle()).await).unwrap_or_else(|_| { + panic!("The argstack is an intermediary value, the argument types are known in advance") + }); + self.arg_stack = Some(atom.own().await); + } else { + let atom = (TAtom::::downcast(arg.handle()).await).unwrap_or_else(|_| { + panic!("This is an intermediary value, the argument types are known in advance") + }); + self.args.push(atom.own().await); + } if self.argc == self.args.len() { - (self.cb)(self.args).await.to_gen().await + exec(async move |handle| { + let rule_ctx = RuleCtx { arg_stk: self.arg_stack.unwrap(), handle }; + (self.cb)(rule_ctx, self.args).await + }) + .await } else { new_atom(self) } @@ -53,6 +67,21 @@ impl OwnedAtom for MacroBodyArgCollector { fn body_name(name: &str, counter: usize) -> String { format!("({name})::{counter}") } +pub struct RuleCtx<'a> { + arg_stk: ArgStack, + handle: ExecHandle<'a>, +} +impl RuleCtx<'_> { + pub fn recur(&self, mt: MacTree) -> impl ToExpr + use<> { + eprintln!("Recursion in macro"); + let arg_stk = self.arg_stk.clone(); + ToExprFuture(resolve(mt, arg_stk)) + } + pub async fn exec(&mut self, val: impl ToExpr) -> OrcRes { + self.handle.exec(val).await + } +} + pub(crate) fn build_macro( prio: Option, own_kws: impl IntoIterator, @@ -74,31 +103,25 @@ impl MacroBuilder { pub(crate) fn rule( mut self, pat: MacTreeSeq, - body: [impl AsyncFn([MacTree; N]) -> R + 'static; 1], + body: [impl AsyncFn(RuleCtx, [MacTree; N]) -> R + 'static; 1], ) -> Self { let [body] = body; let body = Rc::new(body); let name = &body_name(self.own_kws[0], self.body_consts.len()); - self.body_consts.extend(match N { - 0 => lazy(true, name, async move |_| { - let argv = [].into_iter().collect_array().expect("N is 0"); - MemKind::Const(body(argv).await.to_gen().await) - }), - 1.. => cnst( - true, - name, - new_atom(MacroBodyArgCollector { - argc: N, - args: Vec::new(), - cb: Rc::new(move |argv| { - let arr = - argv.into_iter().collect_array::().expect("argc should enforce the length"); - let body = body.clone(); - Box::pin(async move { body(arr).await.to_gen().await }) - }), + self.body_consts.extend(cnst( + true, + name, + new_atom(MacroBodyArgCollector { + argc: N, + arg_stack: None, + args: Vec::new(), + cb: Rc::new(move |rec, argv| { + let arr = argv.into_iter().collect_array::().expect("argc should enforce the length"); + let body = body.clone(); + Box::pin(async move { body(rec, arr).await.to_gen().await }) }), - ), - }); + }), + )); self.patterns.push(pat); self } @@ -282,4 +305,6 @@ macro_rules! mactreev { }; } -pub(crate) use {mactree, mactreev, mactreev_impl}; +pub(crate) use mactree; +pub(crate) use mactreev; +pub(crate) use mactreev_impl; diff --git a/orchid-std/src/std/binary/binary_atom.rs b/orchid-std/src/std/binary/binary_atom.rs index 5e70379..0dc1518 100644 --- a/orchid-std/src/std/binary/binary_atom.rs +++ b/orchid-std/src/std/binary/binary_atom.rs @@ -4,8 +4,7 @@ use std::rc::Rc; use futures::AsyncWrite; use orchid_api_traits::Encode; -use orchid_extension::Atomic; -use orchid_extension::{DeserializeCtx, OwnedAtom, OwnedVariant}; +use orchid_extension::{Atomic, DeserializeCtx, OwnedAtom, OwnedVariant}; #[derive(Clone)] pub struct BlobAtom(pub(crate) Rc>); diff --git a/orchid-std/src/std/boolean.rs b/orchid-std/src/std/boolean.rs index 3bb5f1f..7207b1f 100644 --- a/orchid-std/src/std/boolean.rs +++ b/orchid-std/src/std/boolean.rs @@ -1,10 +1,8 @@ use orchid_api_derive::Coding; use orchid_base::{FmtUnit, OrcRes, sym}; -use orchid_extension::{ToExpr, TryFromExpr}; -use orchid_extension::Expr; use orchid_extension::gen_expr::GExpr; use orchid_extension::tree::{GenMember, cnst, comments, fun, prefix}; -use orchid_extension::{Atomic, TAtom, ThinAtom, ThinVariant}; +use orchid_extension::{Atomic, Expr, TAtom, ThinAtom, ThinVariant, ToExpr, TryFromExpr}; #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding)] pub struct Bool(pub bool); diff --git a/orchid-std/src/std/ops/subscript_lexer.rs b/orchid-std/src/std/ops/subscript_lexer.rs index b251b2a..bd7a301 100644 --- a/orchid-std/src/std/ops/subscript_lexer.rs +++ b/orchid-std/src/std/ops/subscript_lexer.rs @@ -1,8 +1,7 @@ -use orchid_base::{name_char, name_start}; -use orchid_base::{OrcRes, is}; +use orchid_base::{OrcRes, is, name_char, name_start}; use orchid_extension::gen_expr::new_atom; -use orchid_extension::{LexContext, LexedData, Lexer, err_not_applicable}; use orchid_extension::tree::GenTok; +use orchid_extension::{LexContext, LexedData, Lexer, err_not_applicable}; use crate::std::string::str_atom::IntStrAtom; diff --git a/orchid-std/src/std/option.rs b/orchid-std/src/std/option.rs index 6931220..9e3be3a 100644 --- a/orchid-std/src/std/option.rs +++ b/orchid-std/src/std/option.rs @@ -4,11 +4,12 @@ use std::pin::Pin; use futures::AsyncWrite; use orchid_api_traits::Encode; use orchid_base::{is, mk_errv, sym}; -use orchid_extension::{ToExpr, TryFromExpr}; -use orchid_extension::{Expr, ExprHandle}; use orchid_extension::gen_expr::{call, new_atom}; use orchid_extension::tree::{GenMember, cnst, fun, prefix}; -use orchid_extension::{Atomic, DeserializeCtx, ForeignAtom, OwnedAtom, OwnedVariant, TAtom}; +use orchid_extension::{ + Atomic, DeserializeCtx, Expr, ExprHandle, ForeignAtom, OwnedAtom, OwnedVariant, TAtom, ToExpr, + TryFromExpr, +}; use crate::{OrcString, api}; diff --git a/orchid-std/src/std/protocol/parse_impls.rs b/orchid-std/src/std/protocol/parse_impls.rs index bbb7ae3..bfe264b 100644 --- a/orchid-std/src/std/protocol/parse_impls.rs +++ b/orchid-std/src/std/protocol/parse_impls.rs @@ -1,13 +1,9 @@ use itertools::{Itertools, chain}; -use orchid_base::Sym; use orchid_base::{ - Import, Parsed, Snippet, expect_tok, line_items, parse_multiname, token_errv, -}; -use orchid_base::{Paren, Token}; -use orchid_base::{IStr, OrcRes, is, mk_errv}; -use orchid_extension::{ - PTokTree, ParsCtx, ParsedLine, ParsedLineKind, p_tree2gen, p_v2gen, + IStr, Import, OrcRes, Paren, Parsed, Snippet, Sym, Token, expect_tok, is, line_items, mk_errv, + parse_multiname, token_errv, }; +use orchid_extension::{PTokTree, ParsCtx, ParsedLine, ParsedLineKind, p_tree2gen, p_v2gen}; pub async fn parse_impls( _: &ParsCtx<'_>, diff --git a/orchid-std/src/std/protocol/proto_parser.rs b/orchid-std/src/std/protocol/proto_parser.rs index 56fdf2a..bb71234 100644 --- a/orchid-std/src/std/protocol/proto_parser.rs +++ b/orchid-std/src/std/protocol/proto_parser.rs @@ -2,9 +2,8 @@ use std::rc::Rc; use hashbrown::HashMap; use orchid_base::{Comment, OrcRes, Parsed, Token, expect_end, is, mk_errv, sym, try_pop_no_fluff}; -use orchid_extension::ToExpr; use orchid_extension::gen_expr::{call, new_atom}; -use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser}; +use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser, ToExpr}; use crate::std::protocol::parse_impls::parse_impls; use crate::std::protocol::types::Tag; diff --git a/orchid-std/src/std/protocol/type_parser.rs b/orchid-std/src/std/protocol/type_parser.rs index 52f992c..862f6fb 100644 --- a/orchid-std/src/std/protocol/type_parser.rs +++ b/orchid-std/src/std/protocol/type_parser.rs @@ -2,9 +2,8 @@ use std::rc::Rc; use hashbrown::HashMap; use orchid_base::{Comment, OrcRes, Parsed, Token, expect_end, is, mk_errv, sym, try_pop_no_fluff}; -use orchid_extension::ToExpr; use orchid_extension::gen_expr::{call, new_atom}; -use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser}; +use orchid_extension::{PSnippet, ParsCtx, ParsedLine, Parser, ToExpr}; use crate::std::protocol::parse_impls::parse_impls; use crate::std::protocol::types::Tag; diff --git a/orchid-std/src/std/record/record_atom.rs b/orchid-std/src/std/record/record_atom.rs index d890535..990bca7 100644 --- a/orchid-std/src/std/record/record_atom.rs +++ b/orchid-std/src/std/record/record_atom.rs @@ -9,10 +9,8 @@ use hashbrown::HashMap; use orchid_api_derive::Coding; use orchid_api_traits::{Encode, Request}; use orchid_base::{IStr, Receipt, ReqHandle, ReqHandleExt, Sym, es, sym}; -use orchid_extension::ToExpr; -use orchid_extension::Expr; use orchid_extension::{ - Atomic, DeserializeCtx, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, + Atomic, DeserializeCtx, Expr, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, ToExpr, }; use crate::api; diff --git a/orchid-std/src/std/record/record_lib.rs b/orchid-std/src/std/record/record_lib.rs index a0a1c28..8259058 100644 --- a/orchid-std/src/std/record/record_lib.rs +++ b/orchid-std/src/std/record/record_lib.rs @@ -3,10 +3,9 @@ use std::rc::Rc; use hashbrown::HashMap; use itertools::Itertools; use orchid_base::{is, mk_errv}; -use orchid_extension::TAtom; -use orchid_extension::Expr; -use orchid_extension::gen_expr::{arg, new_atom}; +use orchid_extension::gen_expr::new_atom; use orchid_extension::tree::{GenMember, cnst, fun, prefix}; +use orchid_extension::{Expr, TAtom, get_arg_posv}; use crate::std::record::record_atom::RecordAtom; use crate::std::string::str_atom::IntStrAtom; @@ -26,7 +25,7 @@ pub fn gen_record_lib() -> Vec { None => Err(mk_errv( is("Key not found in record").await, format!("{} is not in this record, valid keys are {}", key.0, record.0.keys().join(", ")), - [arg(0).pos.clone(), arg(1).pos.clone()], + get_arg_posv([0, 1]).await, )), } }), diff --git a/orchid-std/src/std/std_system.rs b/orchid-std/src/std/std_system.rs index 68509f0..d5ceaf8 100644 --- a/orchid-std/src/std/std_system.rs +++ b/orchid-std/src/std/std_system.rs @@ -4,10 +4,10 @@ use futures::future::join_all; use orchid_api_derive::{Coding, Hierarchy}; use orchid_base::{Receipt, ReqHandle, ReqHandleExt, Sym, es, sym}; use orchid_extension::gen_expr::new_atom; -use orchid_extension::ParserObj; use orchid_extension::tree::{GenMember, merge_trivial}; use orchid_extension::{ - AtomOps, AtomicFeatures, Expr, LexerObj, ReqForSystem, System, SystemCard, SystemCtor, ToExpr, + AtomOps, AtomicFeatures, Expr, LexerObj, ParserObj, ReqForSystem, System, SystemCard, SystemCtor, + ToExpr, }; use super::number::num_lib::gen_num_lib; diff --git a/orchid-std/src/std/stream/stream_cmds.rs b/orchid-std/src/std/stream/stream_cmds.rs index adeae3b..d0f4bbd 100644 --- a/orchid-std/src/std/stream/stream_cmds.rs +++ b/orchid-std/src/std/stream/stream_cmds.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use never::Never; use orchid_base::{ReqHandleExt, fmt, is, mk_errv}; -use orchid_extension::gen_expr::{bot, call, new_atom}; +use orchid_extension::gen_expr::{bot, call, new_atom, serialize}; use orchid_extension::std_reqs::{ReadLimit, ReadReq, RunCommand}; use orchid_extension::{Atomic, Expr, ForeignAtom, OwnedAtom, OwnedVariant, Supports, ToExpr}; @@ -50,6 +50,6 @@ impl Supports for ReadStreamCmd { ), Some(Ok(v)) => Ok(call(self.succ.clone(), new_atom(BlobAtom(Rc::new(v)))).await), }; - hand.reply(&req, &Some(ret.to_gen().await.serialize().await)).await + hand.reply(&req, &Some(serialize(ret.to_gen().await).await)).await } } diff --git a/orchid-std/src/std/string/str_atom.rs b/orchid-std/src/std/string/str_atom.rs index c7ad1aa..a5e6c08 100644 --- a/orchid-std/src/std/string/str_atom.rs +++ b/orchid-std/src/std/string/str_atom.rs @@ -10,10 +10,9 @@ use orchid_api_traits::{Encode, Request}; use orchid_base::{ FmtCtx, FmtUnit, IStr, OrcRes, Receipt, ReqHandle, ReqHandleExt, Sym, es, is, mk_errv, sym, }; -use orchid_extension::{ToExpr, TryFromExpr}; -use orchid_extension::Expr; use orchid_extension::{ - AtomMethod, Atomic, DeserializeCtx, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, TAtom, + AtomMethod, Atomic, DeserializeCtx, Expr, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, + TAtom, ToExpr, TryFromExpr, }; use crate::std::protocol::types::{GetImpl, ProtocolMethod}; diff --git a/orchid-std/src/std/string/str_lexer.rs b/orchid-std/src/std/string/str_lexer.rs index d3f4fc0..5440e94 100644 --- a/orchid-std/src/std/string/str_lexer.rs +++ b/orchid-std/src/std/string/str_lexer.rs @@ -1,10 +1,8 @@ use itertools::Itertools; use orchid_base::{OrcErr, OrcErrv, OrcRes, Paren, SrcRange, Sym, is, mk_errv, sym, wrap_tokv}; -use orchid_extension::ToExpr; use orchid_extension::gen_expr::new_atom; -use orchid_extension::{LexContext, Lexer, err_not_applicable}; -use orchid_extension::p_tree2gen; use orchid_extension::tree::{GenTok, GenTokTree, ref_tok, x_tok}; +use orchid_extension::{LexContext, Lexer, ToExpr, err_not_applicable, p_tree2gen}; use super::str_atom::IntStrAtom; diff --git a/orchid-std/src/std/time.rs b/orchid-std/src/std/time.rs index 18515ec..9ce4252 100644 --- a/orchid-std/src/std/time.rs +++ b/orchid-std/src/std/time.rs @@ -1,15 +1,14 @@ use std::borrow::Cow; use std::io; -use std::time::Instant; -use chrono::TimeDelta; +use chrono::{DateTime, TimeDelta, Utc}; use never::Never; use orchid_api::ExprTicket; use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_traits::Request; use orchid_base::{Numeric, OrcRes, Receipt, ReqHandle, ReqHandleExt}; -use orchid_extension::gen_expr::{GExpr, call, new_atom}; -use orchid_extension::std_reqs::{AsDuration, RunCommand}; +use orchid_extension::gen_expr::{GExpr, call, new_atom, serialize}; +use orchid_extension::std_reqs::{AsInstant, RunCommand}; use orchid_extension::tree::{GenMember, fun, prefix}; use orchid_extension::{ Atomic, Expr, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, TAtom, ThinAtom, ThinVariant, @@ -44,18 +43,9 @@ impl TryFromExpr for OrcDT { Ok(TAtom::::try_from_expr(expr).await?.value) } } -impl Supports for OrcDT { - async fn handle<'a>( - &self, - hand: Box + '_>, - req: AsDuration, - ) -> std::io::Result> { - hand.reply(&req, &self.0.to_std().unwrap()).await - } -} #[derive(Clone)] -pub struct InstantAtom(Instant); +pub struct InstantAtom(DateTime); impl Atomic for InstantAtom { type Variant = OwnedVariant; type Data = (); @@ -64,6 +54,15 @@ impl OwnedAtom for InstantAtom { type Refs = Never; async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } } +impl Supports for InstantAtom { + async fn handle<'a>( + &self, + hand: Box + '_>, + req: AsInstant, + ) -> std::io::Result> { + hand.reply(&req, &self.0).await + } +} #[derive(Clone)] struct Now(Expr); @@ -82,7 +81,7 @@ impl Supports for Now { hand: Box + '_>, req: RunCommand, ) -> io::Result> { - let cont = call(self.0.clone(), new_atom(InstantAtom(Instant::now()))).await.serialize().await; + let cont = serialize(call(self.0.clone(), new_atom(InstantAtom(Utc::now()))).await).await; hand.reply(&req, &Some(cont)).await } } diff --git a/orchid.code-workspace b/orchid.code-workspace index 9459bdc..3100add 100644 --- a/orchid.code-workspace +++ b/orchid.code-workspace @@ -4,6 +4,72 @@ "path": "." } ], + "tasks": { + "version": "2.0.0", + "tasks": [ + { + "label": "Build All", + "command": "cargo", + "options": { + "cwd": "${workspaceFolder}" + }, + "type": "shell", + "args": ["build"], + "problemMatcher": [ + "$rustc" + ], + "presentation": { + "reveal": "always" + }, + "group": "build" + } + ] + }, + "launch": { + "version": "0.2.0", + "configurations": [ + { + "name": "(LLDB) Launch", + "type": "lldb", + "request": "launch", + "preLaunchTask": "Build All", + "env": { + "ORCHID_EXTENSIONS": "target/debug/orchid_std", + "ORCHID_DEFAULT_SYSTEMS": "orchid::std;orchid::macros", + }, + "cwd": "${workspaceRoot}", + "program": "${workspaceRoot}/target/debug/orcx.exe", + "args": [ + "--logs=stderr", + "--logs=debug>stderr", + "--logs=msg>stderr", + "exec", + "1 + 1" + ] + }, + { + "name": "(Windows) Launch", + "type": "cppvsdbg", + "request": "launch", + "requireExactSource": true, + "preLaunchTask": "Build All", + "environment": [ + { "name": "ORCHID_EXTENSIONS", "value": "target/debug/orchid_std" }, + { "name": "ORCHID_DEFAULT_SYSTEMS", "value": "orchid::std;orchid::macros" }, + ], + "cwd": "${workspaceRoot}", + "program": "${workspaceRoot}/target/debug/orcx.exe", + "args": [ + "--logs=stderr", + "--logs=debug>stderr", + "--logs=msg>stderr", + "exec", + "1 + 1" + ] + } + ], + "compounds": [] + }, "settings": { "[markdown]": { // markdown denotes line breaks with trailing space diff --git a/orcx/Cargo.toml b/orcx/Cargo.toml index a09deda..11b07cf 100644 --- a/orcx/Cargo.toml +++ b/orcx/Cargo.toml @@ -17,5 +17,6 @@ orchid-base = { version = "0.1.0", path = "../orchid-base" } orchid-host = { version = "0.1.0", path = "../orchid-host", features = [ "tokio", ] } +stacker = "0.1.23" substack = "1.1.1" tokio = { version = "1.49.0", features = ["full"] } diff --git a/orcx/src/main.rs b/orcx/src/main.rs index 8b914cc..eda3640 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -2,11 +2,13 @@ use orchid_base::Logger; use orchid_host::dylib::ext_dylib; use tokio::time::Instant; pub mod parse_folder; +mod print_mod; +mod repl; use std::cell::RefCell; use std::collections::HashMap; use std::fs::File; -use std::io::{Read, Write}; +use std::io::Read; use std::pin::pin; use std::process::{Command, ExitCode}; use std::rc::Rc; @@ -20,8 +22,8 @@ use futures::{FutureExt, Stream, TryStreamExt, io}; use itertools::Itertools; use orchid_base::local_interner::local_interner; use orchid_base::{ - FmtCtxImpl, Format, Import, NameLike, Snippet, SrcRange, Token, VPath, fmt, fmt_v, is, log, sym, - take_first, try_with_reporter, ttv_fmt, with_interner, with_logger, with_reporter, with_stash, + FmtCtxImpl, Format, Snippet, SrcRange, Token, VPath, fmt, fmt_v, is, log, sym, take_first, + try_with_reporter, ttv_fmt, with_interner, with_logger, with_reporter, with_stash, }; use orchid_host::ctx::{Ctx, JoinHandle, Spawner}; use orchid_host::execute::{ExecCtx, ExecResult}; @@ -30,15 +32,14 @@ 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::parsed::{ParsTokTree, ParsedModule}; use orchid_host::subprocess::ext_command; use orchid_host::system::init_systems; -use orchid_host::tree::{MemberKind, Module, RootData}; use substack::Substack; -use tokio::io::{AsyncBufReadExt, BufReader, stdin}; use tokio::task::{LocalSet, spawn_local}; use crate::parse_folder::parse_folder; +use crate::repl::repl; #[derive(Parser, Debug)] #[command(version, about, long_about)] @@ -58,8 +59,10 @@ pub struct Args { #[derive(Subcommand, Debug)] pub enum Commands { Lex { - #[arg()] - file: Utf8PathBuf, + #[arg(long)] + file: Option, + #[arg(long)] + line: Option, }, Parse { #[arg(short, long)] @@ -174,311 +177,168 @@ impl Spawner for SpawnerImpl { } } -#[tokio::main] -async fn main() -> io::Result { +fn main() -> io::Result { eprintln!("Orcx v0.1 is free software provided without warranty."); - 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 logger = get_logger(&args); - let logger2 = logger.clone(); - unsafe { STARTUP = Some(Instant::now()) }; - let ctx = Ctx::new(SpawnerImpl, logger2); - let (signal_end_main, on_end_main) = futures::channel::oneshot::channel(); - let ctx1 = ctx.clone(); - local_set.spawn_local(async move { - let ctx = &ctx1; - with_stash(async move { - 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(); - match lex(is(&buf).await, sym!(usercode), &systems, ctx).await { - Ok(lexemes) => - println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true)), - Err(e) => { - eprintln!("{e}"); - exit_code1.replace(ExitCode::FAILURE); + // Use a 10MB stack for single-threaded, unoptimized operation + stacker::grow(10 * 1024 * 1024, || { + tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { + 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 logger = get_logger(&args); + let logger2 = logger.clone(); + unsafe { STARTUP = Some(Instant::now()) }; + let ctx = Ctx::new(SpawnerImpl, logger2); + let (signal_end_main, on_end_main) = futures::channel::oneshot::channel(); + let ctx1 = ctx.clone(); + local_set.spawn_local(async move { + let ctx = &ctx1; + let res = with_stash(async move { + let extensions = + get_all_extensions(&args, ctx).try_collect::>().await.unwrap(); + time_print(&args, "Extensions loaded"); + match args.command { + Commands::Lex { file, line } => { + let (_, systems) = init_systems(&args.system, &extensions).await.unwrap(); + let mut buf = String::new(); + match (&file, &line) { + (Some(file), None) => { + let mut file = File::open(file.as_std_path()).unwrap(); + file.read_to_string(&mut buf).unwrap(); + }, + (None, Some(line)) => buf = line.clone(), + (None, None) | (Some(_), Some(_)) => + return Err("`lex` expected exactly one of --file and --line".to_string()), + }; + let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx) + .await + .map_err(|e| e.to_string())?; + 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) => + 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 first = lexemes.first().ok_or("File empty!".to_string())?; + let pctx = + HostParseCtxImpl { systems: &systems, ctx: ctx.clone(), src: sym!(usercode) }; + let snip = Snippet::new(first, &lexemes); + let ptree = try_with_reporter(parse_items(&pctx, Substack::Bottom, snip)) + .await + .map_err(|e| e.to_string())?; + if ptree.is_empty() { + return Err( + "File empty only after parsing, but no errors were reported".to_string(), + ); + } 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)) + } + }, + Commands::Repl => repl(&args, &extensions, ctx.clone()).await?, + 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(); + root = try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())) + .await + .map_err(|e| e.to_string())? + } + let prefix = match prefix { + Some(pref) => VPath::parse(&pref).await, + None => VPath::new([]), + }; + let root_data = root.0.read().await; + print_mod::print_mod(&root_data.root, prefix, &root_data).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(); + root = try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())) + .await + .map_err(|e| e.to_string())?; + } + let mut lexemes = lex(is(code.trim()).await, path.clone(), &systems, ctx) .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)); - match xctx.execute().await { - 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) => { + .map_err(|e| e.to_string())?; writeln!( log("debug"), "lexed: {}", fmt_v::(lexemes.iter()).await.join(" ") ) .await; - 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 items = + try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet)) + .await + .map_err(|e| e.to_string())?; + let entrypoint = ParsedModule::new(true, items); + let root = with_reporter(root.add_parsed(&entrypoint, path.clone())) + .await + .map_err(|e| e.to_string())?; + let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos()); + let mut xctx = ExecCtx::new(root.clone(), expr).await; + xctx.set_gas(Some(10_000)); + match xctx.execute().await { + 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!"), + } }, }; - 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.clone(), expr).await; - xctx.set_gas(Some(10_000)); - match xctx.execute().await { - 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!"), - } - }, + Ok(()) + }) + .await; + if let Err(s) = res { + eprintln!("{s}"); + *exit_code1.borrow_mut() = ExitCode::FAILURE; + } + signal_end_main.send(()).expect("cleanup should still be waiting"); + }); + let cleanup = async { + if on_end_main.await.is_err() { + return; + } + tokio::time::sleep(Duration::from_secs(2)).await; + let mut extensions = HashMap::new(); + let systems = ctx.systems.read().await.values().filter_map(|v| v.upgrade()).collect_vec(); + let exprs = ctx.exprs.iter().collect_vec(); + for system in &systems { + extensions.insert(system.ext().name().clone(), system.ext().clone()); + } + if extensions.is_empty() && systems.is_empty() && exprs.is_empty() { + return; + } + eprintln!("Shutdown is taking long. The following language constructs are still live:"); + eprintln!("Extensions: {}", extensions.keys().join(", ")); + for sys in &systems { + eprintln!("System: {:?} = {}", sys.id(), sys.ctor().name()) + } + for (rc, expr) in &exprs { + eprintln!("{rc}x {:?} = {}", expr.id(), fmt(expr).await) + } + std::process::abort() }; + futures::future::select( + pin!(cleanup), + pin!(with_interner(local_interner(), with_logger(logger, local_set))), + ) + .await; + let x = *exit_code.borrow(); + Ok(x) }) - .await; - signal_end_main.send(()).expect("cleanup should still be waiting"); - }); - let cleanup = async { - if on_end_main.await.is_err() { - return; - } - tokio::time::sleep(Duration::from_secs(2)).await; - let mut extensions = HashMap::new(); - let systems = ctx.systems.read().await.values().filter_map(|v| v.upgrade()).collect_vec(); - let exprs = ctx.exprs.iter().collect_vec(); - for system in &systems { - extensions.insert(system.ext().name().clone(), system.ext().clone()); - } - if extensions.is_empty() && systems.is_empty() && exprs.is_empty() { - return; - } - eprintln!("Shutdown is taking long. The following language constructs are still live:"); - eprintln!("Extensions: {}", extensions.keys().join(", ")); - for sys in &systems { - eprintln!("System: {:?} = {}", sys.id(), sys.ctor().name()) - } - for (rc, expr) in &exprs { - eprintln!("{rc}x {:?} = {}", expr.id(), fmt(expr).await) - } - std::process::abort() - }; - futures::future::select( - pin!(cleanup), - pin!(with_interner(local_interner(), with_logger(logger, local_set))), - ) - .await; - let x = *exit_code.borrow(); - Ok(x) + }) } diff --git a/orcx/src/parse_folder.rs b/orcx/src/parse_folder.rs index 2ea3044..78c23d0 100644 --- a/orcx/src/parse_folder.rs +++ b/orcx/src/parse_folder.rs @@ -3,10 +3,7 @@ use std::path::{Path, PathBuf}; use futures::FutureExt; use itertools::Itertools; -use orchid_base::SrcRange; -use orchid_base::Sym; -use orchid_base::Snippet; -use orchid_base::{OrcRes, async_io_err, is, os_str_to_string, report}; +use orchid_base::{OrcRes, Snippet, SrcRange, Sym, async_io_err, is, os_str_to_string, report}; use orchid_host::ctx::Ctx; use orchid_host::lex::lex; use orchid_host::parse::{HostParseCtxImpl, parse_items}; diff --git a/orcx/src/print_mod.rs b/orcx/src/print_mod.rs new file mode 100644 index 0000000..61a7541 --- /dev/null +++ b/orcx/src/print_mod.rs @@ -0,0 +1,31 @@ +use futures::FutureExt; +use itertools::Itertools; +use orchid_base::{NameLike, VPath, fmt}; +use orchid_host::tree::{MemKind, Mod, RootData}; + +pub async fn print_mod(module: &Mod, 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 { + MemKind::Module(module) => { + println!("{indent}module {key} {{"); + print_mod(module, VPath::new(new_path.segs()), root).boxed_local().await; + println!("{indent}}}") + }, + MemKind::Const => { + let value = root.consts.get(&new_path).expect("Missing const!"); + println!("{indent}const {key} = {}", fmt(value).await) + }, + } + } +} diff --git a/orcx/src/repl.rs b/orcx/src/repl.rs new file mode 100644 index 0000000..1472c92 --- /dev/null +++ b/orcx/src/repl.rs @@ -0,0 +1,102 @@ +use std::io::Write; + +use itertools::Itertools; +use orchid_base::{ + FmtCtxImpl, FmtTTV, Format, Import, NameLike, Snippet, SrcRange, Token, VPath, fmt, is, log, sym, + take_first, try_with_reporter, with_reporter, +}; +use orchid_host::ctx::Ctx; +use orchid_host::execute::{ExecCtx, ExecResult}; +use orchid_host::expr::ExprKind; +use orchid_host::extension::Extension; +use orchid_host::lex::lex; +use orchid_host::parse::{HostParseCtxImpl, parse_item}; +use orchid_host::parsed::{Item, ItemKind, ParsTokTree, ParsedMember, ParsedModule}; +use orchid_host::system::init_systems; +use substack::Substack; +use tokio::io::{AsyncBufReadExt, BufReader, stdin}; + +use crate::Args; +use crate::print_mod::print_mod; + +pub async fn repl(args: &Args, extensions: &[Extension], ctx: Ctx) -> Result<(), String> { + let mut counter = 0; + let mut imports = Vec::new(); + let usercode_path = sym!(usercode); + let mut stdin = BufReader::new(stdin()); + let (mut root, systems) = init_systems(&args.system, extensions).await.unwrap(); + loop { + counter += 1; + print!("\\.> "); + std::io::stdout().flush().unwrap(); + let mut prompt = String::new(); + stdin.read_line(&mut prompt).await.unwrap(); + if let Some(cmdline) = prompt.trim().strip_prefix(":") { + if cmdline == "help" { + println!( + "Recognized commands are:\n\ + - help print this message\n\ + - modtree display the current module tree" + ) + } else if cmdline == "modtree" { + let root_data = root.0.read().await; + print_mod(&root_data.root, VPath::new([]), &root_data).await + } else { + println!("Could not recognize command. Valid commands are: help") + } + continue; + } + 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: {}", fmt(&FmtTTV(&lexemes)).await).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[..] }; + try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet)) + .await + .map_err(|e| e.to_string()) + }; + 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 import_lines = process_lexemes(&lexemes).await?; + imports.extend(import_lines.into_iter().map(|it| match it.kind { + ItemKind::Import(imp) => imp, + _ => panic!("Expected imports from import line"), + })); + continue; + } + let mut prefix = vec![is("export").await]; + if !discr.is_kw(is("let").await) { + prefix.extend([is("let").await, name.clone(), is("=").await]); + } + lexemes.splice(0..0, prefix.into_iter().map(|n| Token::Name(n).at(prefix_sr.clone()))); + let mut new_lines = process_lexemes(&lexemes).await?; + 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); + root = + with_reporter(root.add_parsed(&new_module, path.clone())).await.map_err(|e| e.to_string())?; + let entrypoint = ExprKind::Const(path.suffix([const_name.clone()]).await).at(input_sr.pos()); + let mut xctx = ExecCtx::new(root.clone(), entrypoint).await; + xctx.set_gas(Some(1000)); + match xctx.execute().await { + 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!"), + } + } +} diff --git a/rustfmt.toml b/rustfmt.toml index b28bf34..bd38593 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -18,6 +18,7 @@ overflow_delimited_expr = true use_small_heuristics = "Max" fn_single_line = true where_single_line = true +format_code_in_doc_comments = false # literals hex_literal_case = "Lower" diff --git a/unsync-pipe/Cargo.toml b/unsync-pipe/Cargo.toml index 50fe493..217edf8 100644 --- a/unsync-pipe/Cargo.toml +++ b/unsync-pipe/Cargo.toml @@ -16,3 +16,4 @@ test_executors = "0.4.1" [dependencies] futures-io = "0.3.31" +itertools = "0.14.0" diff --git a/unsync-pipe/src/lib.rs b/unsync-pipe/src/lib.rs index 2b8224e..a739f8d 100644 --- a/unsync-pipe/src/lib.rs +++ b/unsync-pipe/src/lib.rs @@ -12,6 +12,7 @@ use std::task::{Context, Poll, Waker}; use std::{io, mem}; use futures_io::{AsyncRead, AsyncWrite}; +use itertools::Itertools; fn pipe_layout(bs: usize) -> Layout { Layout::from_size_align(bs, 1).expect("1-align is trivial") } @@ -130,7 +131,15 @@ impl AsyncRingbuffer { } fn writer_wait(&mut self, waker: &Waker) -> Poll> { if self.reader_dropped { - return Poll::Ready(Err(broken_pipe_error())); + let mut buf = vec![0; self.size]; + let count = self.wrapping_read(&mut buf); + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::BrokenPipe, + format!( + "Pipe already closed from reader end with {count}b remaining in the buffer: [{}]", + buf[..count].chunks(4).map(|c| c.iter().map(|b| format!("{b:02x}")).join(" ")).join(" ") + ), + ))); } self.write_waker.drop(); self.write_waker = Trigger::new(waker.clone()); @@ -138,7 +147,10 @@ impl AsyncRingbuffer { } fn reader_wait(&mut self, waker: &Waker) -> Poll> { if self.writer_dropped { - return Poll::Ready(Err(broken_pipe_error())); + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::BrokenPipe, + "Pipe already closed from writer end.", + ))); } self.read_waker.drop(); self.read_waker = Trigger::new(waker.clone()); @@ -150,6 +162,14 @@ impl AsyncRingbuffer { unsafe { &mut *slc }.copy_from_slice(buf); self.write_idx = (self.write_idx + buf.len()) % self.size; } + /// Read a number of bytes from the reader head, then reset the head if + /// necessary. + /// + /// # Safety + /// + /// This function does not check for obstacles such as the writer head or the + /// end of the buffer. It is up to the caller to ensure that the requested + /// number of consecutive bytes is available. unsafe fn non_wrapping_read_unchecked(&mut self, buf: &mut [u8]) { let read_ptr = unsafe { self.start.add(self.read_idx) }; let slc = slice_from_raw_parts(read_ptr, buf.len()).cast_mut(); @@ -188,14 +208,33 @@ impl AsyncRingbuffer { } } } + /// Read as many bytes as possible into the buffer, and return how many bytes + /// were written + fn wrapping_read(&mut self, buf: &mut [u8]) -> usize { + let AsyncRingbuffer { read_idx, write_idx, size, .. } = *self; + if read_idx < write_idx { + // Frontside non-wrapping read + let count = buf.len().min(write_idx - read_idx); + unsafe { self.non_wrapping_read_unchecked(&mut buf[0..count]) }; + count + } else if read_idx + buf.len() < size { + // Backside non-wrapping read + unsafe { self.non_wrapping_read_unchecked(buf) }; + buf.len() + } else { + // Wrapping read + let (end, start) = buf.split_at_mut(size - read_idx); + unsafe { self.non_wrapping_read_unchecked(end) }; + let start_count = start.len().min(write_idx); + unsafe { self.non_wrapping_read_unchecked(&mut start[0..start_count]) }; + end.len() + start_count + } + } } fn already_closed_error() -> io::Error { io::Error::new(io::ErrorKind::BrokenPipe, "Pipe already closed from this end") } -fn broken_pipe_error() -> io::Error { - io::Error::new(io::ErrorKind::BrokenPipe, "Pipe already closed from other end") -} #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum SyncWriteError { @@ -286,37 +325,20 @@ impl AsyncRead for Reader { cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - unsafe { - let data = self.0.as_mut().expect("Cannot be null"); - let AsyncRingbuffer { read_idx, write_idx, size, .. } = *data; - if !buf.is_empty() && data.is_full() { - data.wake_writer(); - } - let poll = if !buf.is_empty() && data.is_empty() { - // Nothing to read, waiting... - data.reader_wait(cx.waker()) - } else if read_idx < write_idx { - // Frontside non-wrapping read - let count = buf.len().min(write_idx - read_idx); - data.non_wrapping_read_unchecked(&mut buf[0..count]); - Poll::Ready(Ok(count)) - } else if read_idx + buf.len() < size { - // Backside non-wrapping read - data.non_wrapping_read_unchecked(buf); - Poll::Ready(Ok(buf.len())) - } else { - // Wrapping read - let (end, start) = buf.split_at_mut(size - read_idx); - data.non_wrapping_read_unchecked(end); - let start_count = start.len().min(write_idx); - data.non_wrapping_read_unchecked(&mut start[0..start_count]); - Poll::Ready(Ok(end.len() + start_count)) - }; - if data.is_empty() { - data.wake_writer(); - } - poll + let data = unsafe { self.0.as_mut().expect("Cannot be null") }; + if !buf.is_empty() && data.is_full() { + data.wake_writer(); } + let poll = if !buf.is_empty() && data.is_empty() { + // Nothing to read, waiting... + data.reader_wait(cx.waker()) + } else { + Poll::Ready(Ok(data.wrapping_read(buf))) + }; + if data.is_empty() { + data.wake_writer(); + } + poll } } impl Drop for Reader {