From b77653f84179a4b15410034577a0a393f94f1374 Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Tue, 30 Sep 2025 21:23:16 +0200 Subject: [PATCH] Added support for defining macros in Rust within the macro system Also fixed a lot of bugs --- Cargo.lock | 24 +++- async-fn-stream/src/lib.rs | 112 +++------------ examples/hello-world/main.orc | 5 +- orchid-api/Cargo.toml | 2 +- orchid-api/src/atom.rs | 10 ++ orchid-api/src/proto.rs | 1 + orchid-base/src/format.rs | 40 +++++- orchid-base/src/reqnot.rs | 2 +- orchid-extension/Cargo.toml | 8 +- orchid-extension/src/atom.rs | 13 +- orchid-extension/src/atom_owned.rs | 15 +- orchid-extension/src/conv.rs | 30 +++- orchid-extension/src/coroutine_exec.rs | 13 +- orchid-extension/src/entrypoint.rs | 12 +- orchid-extension/src/expr.rs | 10 +- orchid-extension/src/func_atom.rs | 13 +- orchid-extension/src/gen_expr.rs | 14 +- orchid-extension/src/reflection.rs | 13 +- orchid-extension/src/system.rs | 12 +- orchid-extension/src/tree.rs | 2 +- orchid-host/Cargo.toml | 2 +- orchid-host/src/atom.rs | 4 +- orchid-host/src/ctx.rs | 2 +- orchid-host/src/dealias.rs | 6 +- orchid-host/src/execute.rs | 10 +- orchid-host/src/expr.rs | 6 +- orchid-host/src/extension.rs | 9 ++ orchid-host/src/parse.rs | 13 +- orchid-host/src/sys_parser.rs | 5 +- orchid-host/src/system.rs | 5 +- orchid-host/src/tree.rs | 2 +- orchid-std/Cargo.toml | 5 +- orchid-std/src/lib.rs | 1 + orchid-std/src/macros/instantiate_tpl.rs | 49 ++++--- orchid-std/src/macros/let_line.rs | 16 ++- orchid-std/src/macros/macro_lib.rs | 80 ++++++----- orchid-std/src/macros/macro_line.rs | 97 ++++--------- orchid-std/src/macros/macro_system.rs | 24 ++-- orchid-std/src/macros/macro_value.rs | 48 +++++++ orchid-std/src/macros/mactree.rs | 78 ++++++----- orchid-std/src/macros/mactree_lexer.rs | 14 +- orchid-std/src/macros/mod.rs | 5 +- orchid-std/src/macros/ph_lexer.rs | 79 +++++++++++ orchid-std/src/macros/recur_state.rs | 71 ---------- orchid-std/src/macros/requests.rs | 67 +++++++++ orchid-std/src/macros/resolve.rs | 41 ++---- orchid-std/src/macros/rule/vec_match.rs | 1 - orchid-std/src/macros/utils.rs | 166 +++++++++++++++++++++++ orchid-std/src/std/number/num_atom.rs | 6 +- orchid-std/src/std/string/str_atom.rs | 10 +- orcx/src/main.rs | 58 +++++++- orcx/src/parse_folder.rs | 20 +-- 52 files changed, 849 insertions(+), 502 deletions(-) create mode 100644 orchid-std/src/macros/macro_value.rs create mode 100644 orchid-std/src/macros/ph_lexer.rs delete mode 100644 orchid-std/src/macros/recur_state.rs create mode 100644 orchid-std/src/macros/requests.rs create mode 100644 orchid-std/src/macros/utils.rs diff --git a/Cargo.lock b/Cargo.lock index b0098bd..1db0b96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,6 +619,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-locks" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" +dependencies = [ + "futures-channel", + "futures-task", + "tokio", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -798,9 +809,9 @@ dependencies = [ [[package]] name = "konst" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b7495a4af30134f36ab2018716ba98b092019a6c5dc2126b94e3241c170748" +checksum = "64896bdfd7906cfb0b57bc04f08bde408bcd6aaf71ff438ee471061cd16f2e86" dependencies = [ "const_panic", "konst_proc_macros", @@ -1029,11 +1040,11 @@ name = "orchid-extension" version = "0.1.0" dependencies = [ "async-fn-stream", - "async-lock", "async-once-cell", "derive_destructure", "dyn-clone", "futures", + "futures-locks", "hashbrown 0.16.0", "include_dir", "itertools", @@ -1060,12 +1071,12 @@ name = "orchid-host" version = "0.1.0" dependencies = [ "async-fn-stream", - "async-lock", "async-once-cell", "async-process", "bound", "derive_destructure", "futures", + "futures-locks", "hashbrown 0.16.0", "itertools", "lazy_static", @@ -1086,6 +1097,7 @@ dependencies = [ name = "orchid-std" version = "0.1.0" dependencies = [ + "async-fn-stream", "async-once-cell", "futures", "hashbrown 0.16.0", @@ -1431,9 +1443,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.37.2" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" +checksum = "c8975fc98059f365204d635119cf9c5a60ae67b841ed49b5422a9a7e56cdfac0" dependencies = [ "arrayvec", "borsh", diff --git a/async-fn-stream/src/lib.rs b/async-fn-stream/src/lib.rs index 2a8af10..1b5ff22 100644 --- a/async-fn-stream/src/lib.rs +++ b/async-fn-stream/src/lib.rs @@ -1,114 +1,44 @@ -use std::cell::Cell; -use std::future::poll_fn; use std::marker::PhantomData; -use std::pin::Pin; -use std::ptr; -use std::task::{Context, Poll}; -use futures::future::LocalBoxFuture; -use futures::{FutureExt, Stream}; - -type YieldSlot<'a, T> = &'a Cell>; +use futures::channel::mpsc; +use futures::stream::{PollNext, select_with_strategy}; +use futures::{FutureExt, SinkExt, Stream, StreamExt}; /// Handle that allows you to emit values on a stream. If you drop /// this, the stream will end and you will not be polled again. -pub struct StreamCtx<'a, T>(&'a Cell>, PhantomData<&'a ()>); +pub struct StreamCtx<'a, T>(mpsc::Sender, PhantomData<&'a ()>); impl StreamCtx<'_, T> { - pub fn emit(&mut self, value: T) -> impl Future { - assert!(self.0.replace(Some(value)).is_none(), "Leftover value in stream"); - let mut state = Poll::Pending; - poll_fn(move |_| std::mem::replace(&mut state, Poll::Ready(()))) + pub async fn emit(&mut self, value: T) { + (self.0.send(value).await) + .expect("Dropped a stream receiver without dropping the driving closure"); } } -enum FnOrFut<'a, T, O> { - Fn(Option) -> LocalBoxFuture<'a, O> + 'a>>), - Fut(LocalBoxFuture<'a, O>), -} - -struct AsyncFnStream<'a, T> { - driver: FnOrFut<'a, T, ()>, - output: Cell>, -} -impl<'a, T> Stream for AsyncFnStream<'a, T> { - type Item = T; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - unsafe { - let self_mut = self.get_unchecked_mut(); - let fut = match &mut self_mut.driver { - FnOrFut::Fut(fut) => fut, - FnOrFut::Fn(f) => { - // safety: the cell is held inline in self, which is pinned. - let cell = ptr::from_ref(&self_mut.output).as_ref().unwrap(); - let fut = f.take().unwrap()(cell); - self_mut.driver = FnOrFut::Fut(fut); - return Pin::new_unchecked(self_mut).poll_next(cx); - }, - }; - match fut.as_mut().poll(cx) { - Poll::Ready(()) => Poll::Ready(None), - Poll::Pending => match self_mut.output.replace(None) { - None => Poll::Pending, - Some(t) => Poll::Ready(Some(t)), - }, - } - } - } -} - -struct AsyncFnTryStream<'a, T, E> { - driver: FnOrFut<'a, T, Result, E>>, - output: Cell>, -} -impl<'a, T, E> Stream for AsyncFnTryStream<'a, T, E> { - type Item = Result; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - unsafe { - let self_mut = self.get_unchecked_mut(); - let fut = match &mut self_mut.driver { - FnOrFut::Fut(fut) => fut, - FnOrFut::Fn(f) => { - // safety: the cell is held inline in self, which is pinned. - let cell = ptr::from_ref(&self_mut.output).as_ref().unwrap(); - let fut = f.take().unwrap()(cell); - self_mut.driver = FnOrFut::Fut(fut); - return Pin::new_unchecked(self_mut).poll_next(cx); - }, - }; - match fut.as_mut().poll(cx) { - Poll::Ready(Ok(_)) => Poll::Ready(None), - Poll::Ready(Err(ex)) => Poll::Ready(Some(Err(ex))), - Poll::Pending => match self_mut.output.replace(None) { - None => Poll::Pending, - Some(t) => Poll::Ready(Some(Ok(t))), - }, - } - } - } -} +fn left_strat(_: &mut ()) -> PollNext { PollNext::Left } /// Create a stream from an async function acting as a coroutine pub fn stream<'a, T: 'a>( f: impl for<'b> AsyncFnOnce(StreamCtx<'b, T>) + 'a, ) -> impl Stream + 'a { - AsyncFnStream { - output: Cell::new(None), - driver: FnOrFut::Fn(Some(Box::new(|t| { - async { f(StreamCtx(t, PhantomData)).await }.boxed_local() - }))), - } + let (send, recv) = mpsc::channel::(1); + let fut = async { f(StreamCtx(send, PhantomData)).await }; + // use options to ensure that the stream is driven to exhaustion + select_with_strategy(fut.into_stream().map(|()| None), recv.map(|t| Some(t)), left_strat) + .filter_map(async |opt| opt) } /// Create a stream of result from a fallible function. pub fn try_stream<'a, T: 'a, E: 'a>( f: impl for<'b> AsyncFnOnce(StreamCtx<'b, T>) -> Result, E> + 'a, ) -> impl Stream> + 'a { - AsyncFnTryStream { - output: Cell::new(None), - driver: FnOrFut::Fn(Some(Box::new(|t| { - async { f(StreamCtx(t, PhantomData)).await }.boxed_local() - }))), - } + let (send, recv) = mpsc::channel::(1); + let fut = async { f(StreamCtx(send, PhantomData)).await }; + select_with_strategy( + fut.into_stream().map(|res| if let Err(e) = res { Some(Err(e)) } else { None }), + recv.map(|t| Some(Ok(t))), + left_strat, + ) + .filter_map(async |opt| opt) } #[cfg(test)] diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index d8a6b9d..0a62eed 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -1,2 +1,3 @@ -let user = "dave" -let main = println "Hello $user!" exit_status::success +import macros::common::(+ *) + +let main = 1 + 2 diff --git a/orchid-api/Cargo.toml b/orchid-api/Cargo.toml index 79c6b20..f860d20 100644 --- a/orchid-api/Cargo.toml +++ b/orchid-api/Cargo.toml @@ -13,4 +13,4 @@ futures = { version = "0.3.31", features = ["std"], default-features = false } itertools = "0.14.0" [dev-dependencies] -test_executors = "0.3.2" +test_executors = "0.3.5" diff --git a/orchid-api/src/atom.rs b/orchid-api/src/atom.rs index 78d6bd3..f437d4d 100644 --- a/orchid-api/src/atom.rs +++ b/orchid-api/src/atom.rs @@ -145,6 +145,16 @@ impl Request for ExtAtomPrint { type Response = FormattingUnit; } +/// Can specify the recipient of an atom as well. The main use case for this is +/// to be able to return an atom to other extensions, so it can be combined with +/// a [crate::Move]. +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(ExtHostReq)] +pub struct CreateAtom(pub Atom, pub SysId); +impl Request for CreateAtom { + type Response = ExprTicket; +} + /// Requests that apply to an existing atom instance #[derive(Clone, Debug, Coding, Hierarchy)] #[extends(HostExtReq)] diff --git a/orchid-api/src/proto.rs b/orchid-api/src/proto.rs index 8df1d73..11576e3 100644 --- a/orchid-api/src/proto.rs +++ b/orchid-api/src/proto.rs @@ -85,6 +85,7 @@ pub enum ExtHostReq { IntReq(interner::IntReq), Fwd(atom::Fwd), ExtAtomPrint(atom::ExtAtomPrint), + CreateAtom(atom::CreateAtom), SysFwd(system::SysFwd), ExprReq(expr::ExprReq), SubLex(lexer::SubLex), diff --git a/orchid-base/src/format.rs b/orchid-base/src/format.rs index 20ea3fd..74b3f94 100644 --- a/orchid-base/src/format.rs +++ b/orchid-base/src/format.rs @@ -1,3 +1,4 @@ +use std::borrow::Borrow; use std::cmp::Ordering; use std::convert::Infallible; use std::future::Future; @@ -5,6 +6,7 @@ use std::iter; use std::rc::Rc; use std::str::FromStr; +use futures::future::join_all; use itertools::Itertools; use never::Never; use regex::Regex; @@ -77,9 +79,12 @@ impl FmtElement { pub fn bounded(i: u32) -> Self { Self::sub(i, Some(true)) } pub fn unbounded(i: u32) -> Self { Self::sub(i, Some(false)) } pub fn last(i: u32) -> Self { Self::sub(i, None) } - pub fn sequence(len: usize, bounded: Option) -> impl Iterator { - let len32: u32 = len.try_into().unwrap(); - (0..len32 - 1).map(FmtElement::unbounded).chain([FmtElement::sub(len32 - 1, bounded)]) + pub fn sequence(len: usize, bounded: Option) -> Vec { + match len.try_into().unwrap() { + 0u32 => vec![], + 1u32 => vec![FmtElement::sub(0, bounded)], + n => (0..n - 1).map(FmtElement::unbounded).chain([FmtElement::sub(n - 1, bounded)]).collect(), + } } pub fn from_api(api: &api::FormattingElement) -> Self { match_mapping!(api, api::FormattingElement => FmtElement { @@ -109,6 +114,13 @@ fn variants_parse_test() { println!("final: {vars:?}") } +/// Represents a collection of formatting strings for the same set of parameters +/// from which the formatter can choose within their associated constraints. +/// +/// - {0b} can be replaced by any variant of the parameter. +/// - {0} can only be replaced by a bounded variant of the parameter +/// - {0l} causes the current end restriction to be applied to the parameter. +/// This is to be used if the parameter is at the very end of the variant. #[derive(Clone, Debug, Hash, PartialEq, Eq, Default)] pub struct Variants(pub Vec); impl Variants { @@ -183,19 +195,24 @@ impl Variants { fn add(&mut self, bounded: bool, s: &'_ str) { self.0.push(Variant { bounded, elements: Self::parse(s) }) } - // This option is available in all positions + /// This option is available in all positions. + /// See [Variants] for a description of the format strings pub fn bounded(mut self, s: &'_ str) -> Self { self.add(true, s); self } - // This option is only available in positions immediately preceding the end of - // the sequence or a parenthesized subsequence. + /// This option is only available in positions immediately preceding the end + /// of the sequence or a parenthesized subsequence. + /// See [Variants] for a description of the format strings pub fn unbounded(mut self, s: &'_ str) -> Self { self.add(false, s); self } pub fn sequence(len: usize, delim: &str, seq_bnd: Option) -> Rc { - let seq = Itertools::intersperse(FmtElement::sequence(len, seq_bnd), FmtElement::str(delim)); + let seq = Itertools::intersperse( + FmtElement::sequence(len, seq_bnd).into_iter(), + FmtElement::str(delim), + ); Rc::new(Variants(vec![Variant { bounded: true, elements: seq.collect_vec() }])) } pub fn units(self: &Rc, subs: impl IntoIterator) -> FmtUnit { @@ -278,3 +295,12 @@ impl Format for Never { /// Format with default strategy. Currently equal to [take_first_fmt] pub async fn fmt(v: &(impl Format + ?Sized), i: &Interner) -> String { take_first_fmt(v, i).await } +/// Format a sequence with default strategy. Currently equal to [take_first_fmt] +pub async fn fmt_v>( + v: impl IntoIterator, + i: &Interner, +) -> impl Iterator { + join_all(v.into_iter().map(|f| async move { take_first_fmt(f.borrow(), i).await })) + .await + .into_iter() +} diff --git a/orchid-base/src/reqnot.rs b/orchid-base/src/reqnot.rs index 46532e9..6b9c548 100644 --- a/orchid-base/src/reqnot.rs +++ b/orchid-base/src/reqnot.rs @@ -145,7 +145,7 @@ impl ReqNot { notif_cb(notif_val, self.clone()).await } else if 0 < id.bitand(1 << 63) { let mut sender = g.responses.remove(&!id).expect("Received response for invalid message"); - sender.send(message.to_vec()).await.unwrap() + let _ = sender.send(message.to_vec()).await; } else { let message = ::Req::decode(Pin::new(&mut &payload[..])).await; let mut req_cb = clone_box(&*g.req); diff --git a/orchid-extension/Cargo.toml b/orchid-extension/Cargo.toml index 3ddcfe2..e58929b 100644 --- a/orchid-extension/Cargo.toml +++ b/orchid-extension/Cargo.toml @@ -7,18 +7,18 @@ edition = "2024" [dependencies] async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" } -async-lock = "3.4.1" async-once-cell = "0.5.4" derive_destructure = "1.0.0" dyn-clone = "1.0.20" futures = { version = "0.3.31", features = [ - "std", - "async-await", + "std", + "async-await", ], default-features = false } +futures-locks = "0.7.1" hashbrown = "0.16.0" include_dir = { version = "0.7.4", optional = true } itertools = "0.14.0" -konst = "0.4.1" +konst = "0.4.2" lazy_static = "1.5.0" memo-map = "0.3.3" never = "0.1.0" diff --git a/orchid-extension/src/atom.rs b/orchid-extension/src/atom.rs index d44f46f..92a17d6 100644 --- a/orchid-extension/src/atom.rs +++ b/orchid-extension/src/atom.rs @@ -109,8 +109,8 @@ impl ForeignAtom { .await?; Some(M::Response::decode(Pin::new(&mut &rep[..])).await) } - pub async fn downcast(self) -> Result, NotTypAtom> { - TypAtom::downcast(self.ex().handle()).await + pub async fn downcast(self) -> Result, NotTypAtom> { + TAtom::downcast(self.ex().handle()).await } } impl fmt::Display for ForeignAtom { @@ -222,11 +222,12 @@ impl Default for MethodSetBuilder { } #[derive(Clone)] -pub struct TypAtom { +pub struct TAtom { pub untyped: ForeignAtom, pub value: A::Data, } -impl TypAtom { +impl TAtom { + pub fn ex(&self) -> Expr { self.untyped.clone().ex() } pub fn ctx(&self) -> &SysCtx { self.untyped.ctx() } pub fn i(&self) -> &Interner { self.ctx().i() } pub async fn downcast(expr: Rc) -> Result { @@ -262,11 +263,11 @@ impl TypAtom { .await } } -impl Deref for TypAtom { +impl Deref for TAtom { type Target = A::Data; fn deref(&self) -> &Self::Target { &self.value } } -impl ToExpr for TypAtom { +impl ToExpr for TAtom { async fn to_expr(self) -> GExpr { self.untyped.to_expr().await } } diff --git a/orchid-extension/src/atom_owned.rs b/orchid-extension/src/atom_owned.rs index cec11e9..488b3f0 100644 --- a/orchid-extension/src/atom_owned.rs +++ b/orchid-extension/src/atom_owned.rs @@ -1,16 +1,17 @@ use std::any::{Any, TypeId, type_name}; use std::borrow::Cow; use std::future::Future; +use std::marker::PhantomData; use std::num::NonZero; use std::ops::Deref; use std::pin::Pin; use std::sync::atomic::AtomicU64; -use async_lock::{RwLock, RwLockReadGuard}; use async_once_cell::OnceCell; use dyn_clone::{DynClone, clone_box}; use futures::future::{LocalBoxFuture, ready}; use futures::{AsyncRead, AsyncWrite, FutureExt}; +use futures_locks::{RwLock, RwLockReadGuard}; use itertools::Itertools; use memo_map::MemoMap; use never::Never; @@ -22,7 +23,7 @@ use orchid_base::name::Sym; use crate::api; use crate::atom::{ AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet, - MethodSetBuilder, TypAtom, err_not_callable, err_not_command, get_info, + MethodSetBuilder, TAtom, err_not_callable, err_not_command, get_info, }; use crate::expr::Expr; use crate::gen_expr::{GExpr, bot}; @@ -53,16 +54,16 @@ impl> AtomicFeaturesImpl { id: api::AtomId, - guard: RwLockReadGuard<'a, MemoMap>>, + _lock: PhantomData<&'a ()>, + guard: RwLockReadGuard>>, } impl<'a> AtomReadGuard<'a> { async fn new(id: api::AtomId, ctx: &'a SysCtx) -> Self { let guard = ctx.get_or_default::().objects.read().await; if guard.get(&id).is_none() { - let valid = guard.iter().map(|i| i.0).collect_vec(); - panic!("Received invalid atom ID: {id:?} not in {valid:?}"); + panic!("Received invalid atom ID: {id:?}"); } - Self { id, guard } + Self { id, guard, _lock: PhantomData } } } impl Deref for AtomReadGuard<'_> { @@ -317,7 +318,7 @@ pub(crate) struct ObjStore { } impl SysCtxEntry for ObjStore {} -pub async fn own(typ: TypAtom) -> A { +pub async fn own(typ: TAtom) -> A { let ctx = typ.untyped.ctx(); let g = ctx.get_or_default::().objects.read().await; let atom_id = typ.untyped.atom.drop.expect("Owned atoms always have a drop ID"); diff --git a/orchid-extension/src/conv.rs b/orchid-extension/src/conv.rs index 5b49328..5a0d05e 100644 --- a/orchid-extension/src/conv.rs +++ b/orchid-extension/src/conv.rs @@ -1,11 +1,14 @@ use std::future::Future; +use std::pin::Pin; +use dyn_clone::DynClone; use never::Never; use orchid_base::error::{OrcErrv, OrcRes, mk_errv}; use orchid_base::interner::Interner; use orchid_base::location::Pos; +use trait_set::trait_set; -use crate::atom::{AtomicFeatures, ForeignAtom, ToAtom, TypAtom}; +use crate::atom::{AtomicFeatures, ForeignAtom, TAtom, ToAtom}; use crate::expr::Expr; use crate::gen_expr::{GExpr, atom, bot}; use crate::system::{SysCtx, downcast_atom}; @@ -41,7 +44,7 @@ impl TryFromExpr for ForeignAtom { } } -impl TryFromExpr for TypAtom { +impl TryFromExpr for TAtom { async fn try_from_expr(expr: Expr) -> OrcRes { let f = ForeignAtom::try_from_expr(expr).await?; match downcast_atom::(f).await { @@ -59,6 +62,29 @@ pub trait ToExpr { fn to_expr(self) -> impl Future; } +pub trait ToExprDyn { + fn to_expr_dyn<'a>(self: Box) -> Pin + 'a>> + where Self: 'a; +} +impl ToExprDyn for T { + fn to_expr_dyn<'a>(self: Box) -> Pin + 'a>> + where Self: 'a { + Box::pin(self.to_expr()) + } +} +trait_set! { + pub trait ClonableToExprDyn = ToExprDyn + DynClone; +} +impl ToExpr for Box { + async fn to_expr(self) -> GExpr { self.to_expr_dyn().await } +} +impl ToExpr for Box { + async fn to_expr(self) -> GExpr { self.to_expr_dyn().await } +} +impl Clone for Box { + fn clone(&self) -> Self { dyn_clone::clone_box(&**self) } +} + impl ToExpr for GExpr { async fn to_expr(self) -> GExpr { self } } diff --git a/orchid-extension/src/coroutine_exec.rs b/orchid-extension/src/coroutine_exec.rs index f528e92..812b48d 100644 --- a/orchid-extension/src/coroutine_exec.rs +++ b/orchid-extension/src/coroutine_exec.rs @@ -35,15 +35,12 @@ impl BuilderCoroutine { match cmd { None => panic!("Before the stream ends, we should have gotten a Halt"), Some(Command::Halt(expr)) => expr, - Some(Command::Execute(expr, reply)) => call([ - lambda(0, [seq([ - arg(0), - call([Replier { reply, builder: self }.to_expr().await, arg(0)]), - ])]), - expr, - ]), + Some(Command::Execute(expr, reply)) => call( + lambda(0, seq([arg(0)], call(Replier { reply, builder: self }.to_expr().await, [arg(0)]))), + [expr], + ), Some(Command::Register(expr, reply)) => - call([Replier { reply, builder: self }.to_expr().await, expr]), + call(Replier { reply, builder: self }.to_expr().await, [expr]), } } } diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs index a9fce35..e3ea16b 100644 --- a/orchid-extension/src/entrypoint.rs +++ b/orchid-extension/src/entrypoint.rs @@ -5,11 +5,11 @@ use std::num::NonZero; use std::pin::Pin; use std::rc::Rc; -use async_lock::RwLock; use futures::channel::mpsc::{Receiver, Sender, channel}; use futures::future::{LocalBoxFuture, join_all}; use futures::lock::Mutex; use futures::{FutureExt, SinkExt, StreamExt, stream, stream_select}; +use futures_locks::RwLock; use hashbrown::HashMap; use itertools::Itertools; use orchid_api_traits::{Decode, UnderRoot, enc_vec}; @@ -145,10 +145,7 @@ pub fn extension_init( clone!(exit_send mut); async move { match n { - api::HostExtNotif::Exit => { - eprintln!("Exit received"); - exit_send.send(()).await.unwrap() - }, + api::HostExtNotif::Exit => exit_send.send(()).await.unwrap(), } } .boxed_local() @@ -194,13 +191,14 @@ pub fn extension_init( .then(|mem| { let lazy_mems = &lazy_members; clone!(i, ctx; async move { + let name = i.i(&mem.name).await; let mut tia_ctx = TreeIntoApiCtxImpl { lazy_members: &mut *lazy_mems.lock().await, sys: ctx, basepath: &[], - path: Substack::Bottom, + path: Substack::Bottom.push(name.clone()), }; - (i.i(&mem.name).await.to_api(), mem.kind.into_api(&mut tia_ctx).await) + (name.to_api(), mem.kind.into_api(&mut tia_ctx).await) }) }) .collect() diff --git a/orchid-extension/src/expr.rs b/orchid-extension/src/expr.rs index aa14677..17ea4e1 100644 --- a/orchid-extension/src/expr.rs +++ b/orchid-extension/src/expr.rs @@ -52,10 +52,7 @@ impl ExprHandle { /// nothing, otherwise send an Acquire pub async fn drop_one(self: Rc) { match Rc::try_unwrap(self) { - Err(rc) => { - eprintln!("Extending lifetime for {:?}", rc.tk); - rc.ctx.reqnot().notify(api::Acquire(rc.ctx.sys_id(), rc.tk)).await - }, + Err(rc) => rc.ctx.reqnot().notify(api::Acquire(rc.ctx.sys_id(), rc.tk)).await, Ok(hand) => { // avoid calling destructor hand.destructure(); @@ -65,10 +62,7 @@ impl ExprHandle { /// Drop the handle and get the ticket without a release notification. /// Use this with messages that imply ownership transfer. This function is /// safe because abusing it is a memory leak. - pub fn serialize(self) -> api::ExprTicket { - eprintln!("Skipping destructor for {:?}", self.tk); - self.destructure().0 - } + pub fn serialize(self) -> api::ExprTicket { self.destructure().0 } } impl Eq for ExprHandle {} impl PartialEq for ExprHandle { diff --git a/orchid-extension/src/func_atom.rs b/orchid-extension/src/func_atom.rs index cddc8b4..907ccb6 100644 --- a/orchid-extension/src/func_atom.rs +++ b/orchid-extension/src/func_atom.rs @@ -43,7 +43,7 @@ struct FunRecord { fun: Rc, } -async fn process_args>( +fn process_args>( debug: impl AsRef + Clone + 'static, f: F, ) -> FunRecord { @@ -83,7 +83,7 @@ impl Fun { let record = if let Some(record) = fung.get(&path) { record.clone() } else { - let record = process_args(path.to_string(), f).await; + let record = process_args(path.to_string(), f); fung.insert(path.clone(), record.clone()); record }; @@ -134,11 +134,8 @@ pub struct Lambda { record: FunRecord, } impl Lambda { - pub async fn new>( - debug: impl AsRef + Clone + 'static, - f: F, - ) -> Self { - Self { args: vec![], record: process_args(debug, f).await } + pub fn new>(debug: impl AsRef + Clone + 'static, f: F) -> Self { + Self { args: vec![], record: process_args(debug, f) } } } impl Atomic for Lambda { @@ -176,7 +173,7 @@ mod expr_func_derives { impl< $($t: TryFromExpr + 'static, )* Out: ToExpr, - Func: AsyncFn($($t,)*) -> Out + Clone + Send + Sync + 'static + Func: AsyncFn($($t,)*) -> Out + Clone + 'static > ExprFunc<($($t,)*), Out> for Func { fn argtyps() -> &'static [TypeId] { static STORE: OnceLock> = OnceLock::new(); diff --git a/orchid-extension/src/gen_expr.rs b/orchid-extension/src/gen_expr.rs index 289867e..b78a591 100644 --- a/orchid-extension/src/gen_expr.rs +++ b/orchid-extension/src/gen_expr.rs @@ -105,7 +105,7 @@ fn inherit(kind: GExprKind) -> GExpr { GExpr { pos: Pos::Inherit, kind } } pub fn sym_ref(path: Sym) -> GExpr { inherit(GExprKind::Const(path)) } pub fn atom(atom: A) -> GExpr { inherit(GExprKind::NewAtom(atom.to_atom_factory())) } -pub fn seq(ops: impl IntoIterator) -> GExpr { +pub fn seq(deps: impl IntoIterator, val: GExpr) -> GExpr { fn recur(mut ops: impl Iterator) -> Option { let op = ops.next()?; Some(match recur(ops) { @@ -113,19 +113,15 @@ pub fn seq(ops: impl IntoIterator) -> GExpr { Some(rec) => inherit(GExprKind::Seq(Box::new(op), Box::new(rec))), }) } - recur(ops.into_iter()).expect("Empty list provided to seq!") + recur(deps.into_iter().chain([val])).expect("Empty list provided to seq!") } pub fn arg(n: u64) -> GExpr { inherit(GExprKind::Arg(n)) } -pub fn lambda(n: u64, b: impl IntoIterator) -> GExpr { - inherit(GExprKind::Lambda(n, Box::new(call(b)))) -} +pub fn lambda(n: u64, b: GExpr) -> GExpr { inherit(GExprKind::Lambda(n, Box::new(b))) } -pub fn call(v: impl IntoIterator) -> GExpr { - v.into_iter() - .reduce(|f, x| inherit(GExprKind::Call(Box::new(f), Box::new(x)))) - .expect("Empty call expression") +pub fn call(f: GExpr, argv: impl IntoIterator) -> GExpr { + (argv.into_iter()).fold(f, |f, x| inherit(GExprKind::Call(Box::new(f), Box::new(x)))) } pub fn bot(ev: impl IntoIterator) -> GExpr { diff --git a/orchid-extension/src/reflection.rs b/orchid-extension/src/reflection.rs index 1d000f3..dc6d7ea 100644 --- a/orchid-extension/src/reflection.rs +++ b/orchid-extension/src/reflection.rs @@ -11,23 +11,25 @@ use orchid_base::reqnot::Requester; use crate::api; use crate::system::{SysCtx, SysCtxEntry, WeakSysCtx}; +#[derive(Debug)] pub struct ReflMemData { // None for inferred steps public: OnceCell, kind: ReflMemKind, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ReflMem(Rc); impl ReflMem { pub fn kind(&self) -> ReflMemKind { self.0.kind.clone() } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum ReflMemKind { Const, Mod(ReflMod), } +#[derive(Debug)] pub struct ReflModData { inferred: Mutex, path: VPath, @@ -35,7 +37,7 @@ pub struct ReflModData { members: MemoMap, ReflMem>, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ReflMod(Rc); impl ReflMod { fn ctx(&self) -> SysCtx { @@ -116,8 +118,8 @@ impl ReflMod { Err(api::LsModuleError::InvalidPath) => Err(InvalidPathError { keep_ancestry: false }), Err(api::LsModuleError::IsConstant) => { let const_mem = default_member(self.is_root(), ReflMemKind::Const); - self.0.members.insert(next.clone(), const_mem); - Err(InvalidPathError { keep_ancestry: true }) + self.0.members.insert(next.clone(), const_mem.clone()); + Ok(const_mem) }, Err(api::LsModuleError::TreeUnavailable) => unreachable!(), }; @@ -136,6 +138,7 @@ impl ReflMod { struct ReflRoot(ReflMod); impl SysCtxEntry for ReflRoot {} +#[derive(Clone, Debug)] pub struct InvalidPathError { keep_ancestry: bool, } diff --git a/orchid-extension/src/system.rs b/orchid-extension/src/system.rs index 2eb05ff..1442160 100644 --- a/orchid-extension/src/system.rs +++ b/orchid-extension/src/system.rs @@ -16,7 +16,7 @@ use orchid_base::name::Sym; use orchid_base::reqnot::{Receipt, ReqNot}; use crate::api; -use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId, AtomicFeatures, ForeignAtom, TypAtom, get_info}; +use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId, AtomicFeatures, ForeignAtom, TAtom, get_info}; use crate::coroutine_exec::Replier; use crate::entrypoint::ExtReq; use crate::func_atom::{Fun, Lambda}; @@ -115,7 +115,7 @@ impl DynSystem for T { fn card(&self) -> &dyn DynSystemCard { self } } -pub async fn downcast_atom(foreign: ForeignAtom) -> Result, ForeignAtom> +pub async fn downcast_atom(foreign: ForeignAtom) -> Result, ForeignAtom> where A: AtomicFeatures { let mut data = &foreign.atom.data.0[..]; let ctx = foreign.ctx().clone(); @@ -128,13 +128,16 @@ where A: AtomicFeatures { .ok_or_else(|| foreign.clone())? .get_card() }; + if owner.atoms().flatten().all(|dynfo| dynfo.tid() != TypeId::of::()) { + return Err(foreign); + } let (typ_id, dynfo) = get_info::(owner); if value != typ_id { return Err(foreign); } let val = dynfo.decode(AtomCtx(data, foreign.atom.drop, ctx)).await; let value = *val.downcast::().expect("atom decode returned wrong type"); - Ok(TypAtom { value, untyped: foreign }) + Ok(TAtom { value, untyped: foreign }) } #[derive(Clone)] @@ -142,6 +145,9 @@ pub struct WeakSysCtx(Weak>>); impl WeakSysCtx { pub fn upgrade(&self) -> Option { Some(SysCtx(self.0.upgrade()?)) } } +impl fmt::Debug for WeakSysCtx { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "WeakSysCtx") } +} #[derive(Clone)] pub struct SysCtx(Rc>>); diff --git a/orchid-extension/src/tree.rs b/orchid-extension/src/tree.rs index 52bbea0..7afc75c 100644 --- a/orchid-extension/src/tree.rs +++ b/orchid-extension/src/tree.rs @@ -173,7 +173,7 @@ impl GenMember { let name = ctx.sys().i().i::(&self.name).await; let kind = self.kind.into_api(&mut ctx.push_path(name.clone())).await; let comments = - join_all(self.comments.iter().map(|cmt| async { ctx.sys().i().i(cmt).await.to_api() })).await; + join_all(self.comments.iter().map(async |cmt| ctx.sys().i().i(cmt).await.to_api())).await; api::Member { kind, name: name.to_api(), comments, exported: self.public } } } diff --git a/orchid-host/Cargo.toml b/orchid-host/Cargo.toml index 4616a4a..151cbc7 100644 --- a/orchid-host/Cargo.toml +++ b/orchid-host/Cargo.toml @@ -7,12 +7,12 @@ edition = "2024" [dependencies] async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" } -async-lock = "3.4.1" async-once-cell = "0.5.4" async-process = "2.4.0" bound = "0.6.0" derive_destructure = "1.0.0" futures = { version = "0.3.31", features = ["std"], default-features = false } +futures-locks = "0.7.1" hashbrown = "0.16.0" itertools = "0.14.0" lazy_static = "1.5.0" diff --git a/orchid-host/src/atom.rs b/orchid-host/src/atom.rs index 32bd41b..50a83d1 100644 --- a/orchid-host/src/atom.rs +++ b/orchid-host/src/atom.rs @@ -1,7 +1,7 @@ use std::fmt; use std::rc::{Rc, Weak}; -use async_lock::OnceCell; +use async_once_cell::OnceCell; use derive_destructure::destructure; use orchid_base::format::{FmtCtx, FmtUnit, Format, take_first_fmt}; use orchid_base::location::Pos; @@ -85,7 +85,7 @@ impl AtomHand { } impl Format for AtomHand { async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { - (self.0.display.get_or_init(|| async { + (self.0.display.get_or_init(async { FmtUnit::from_api(&self.0.owner.reqnot().request(api::AtomPrint(self.0.api_ref())).await) })) .await diff --git a/orchid-host/src/ctx.rs b/orchid-host/src/ctx.rs index f653409..268d31a 100644 --- a/orchid-host/src/ctx.rs +++ b/orchid-host/src/ctx.rs @@ -3,7 +3,7 @@ use std::num::{NonZero, NonZeroU16}; use std::rc::{Rc, Weak}; use std::{fmt, ops}; -use async_lock::RwLock; +use futures_locks::RwLock; use hashbrown::HashMap; use orchid_base::builtin::Spawner; use orchid_base::interner::Interner; diff --git a/orchid-host/src/dealias.rs b/orchid-host/src/dealias.rs index 041aeec..a158041 100644 --- a/orchid-host/src/dealias.rs +++ b/orchid-host/src/dealias.rs @@ -48,13 +48,15 @@ pub async fn absolute_path( ) -> Result { let i_self = i.i("self").await; let i_super = i.i("super").await; - let relative = rel.first().is_some_and(|s| *s != i_self && *s != i_super); - if let Some((_, tail)) = rel.split_first().filter(|(h, _)| **h != i_self) { + let mut relative = false; + if let Some((_, tail)) = rel.split_first().filter(|(h, _)| **h == i_self) { rel = tail; + relative = true; } else { while let Some((_, tail)) = rel.split_first().filter(|(h, _)| **h == i_super) { cwd = cwd.split_last().ok_or(AbsPathError::TooManySupers)?.1; rel = tail; + relative = true; } } if relative { VName::new(cwd.iter().chain(rel).cloned()) } else { VName::new(rel.to_vec()) } diff --git a/orchid-host/src/execute.rs b/orchid-host/src/execute.rs index 4744793..c965fe8 100644 --- a/orchid-host/src/execute.rs +++ b/orchid-host/src/execute.rs @@ -1,8 +1,8 @@ use std::mem; -use async_lock::RwLockWriteGuard; use bound::Bound; use futures::FutureExt; +use futures_locks::{RwLockWriteGuard, TryLockError}; use orchid_base::error::OrcErrv; use orchid_base::format::{FmtCtxImpl, Format, take_first}; use orchid_base::location::Pos; @@ -12,7 +12,7 @@ use crate::ctx::Ctx; use crate::expr::{Expr, ExprKind, PathSet, Step}; use crate::tree::Root; -type ExprGuard = Bound, Expr>; +type ExprGuard = Bound, Expr>; /// The stack operation associated with a transform enum StackOp { @@ -76,13 +76,13 @@ impl ExecCtx { #[must_use] pub async fn unpack_ident(&self, ex: &Expr) -> Expr { match ex.kind().try_write().as_deref_mut() { - Some(ExprKind::Identity(ex)) => { + Ok(ExprKind::Identity(ex)) => { let val = self.unpack_ident(ex).boxed_local().await; *ex = val.clone(); val }, - Some(_) => ex.clone(), - None => panic!("Cycle encountered!"), + Ok(_) => ex.clone(), + Err(TryLockError) => panic!("Cycle encountered!"), } } pub async fn execute(&mut self) { diff --git a/orchid-host/src/expr.rs b/orchid-host/src/expr.rs index 7e36d56..de4fb88 100644 --- a/orchid-host/src/expr.rs +++ b/orchid-host/src/expr.rs @@ -4,8 +4,8 @@ use std::num::NonZeroU64; use std::rc::{Rc, Weak}; use std::{fmt, mem}; -use async_lock::RwLock; use futures::FutureExt; +use futures_locks::RwLock; use itertools::Itertools; use orchid_base::error::OrcErrv; use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants}; @@ -41,9 +41,9 @@ impl Expr { pub async fn try_into_owned_atom(self) -> Result { match Rc::try_unwrap(self.0) { Err(e) => Err(Self(e)), - Ok(data) => match data.kind.into_inner() { + Ok(data) => match data.kind.try_unwrap().expect("This fields shouldn't be copied") { ExprKind::Atom(a) => Ok(a), - inner => Err(Self(Rc::new(ExprData { kind: inner.into(), pos: data.pos }))), + inner => Err(Self(Rc::new(ExprData { kind: RwLock::new(inner), pos: data.pos }))), }, } } diff --git a/orchid-host/src/extension.rs b/orchid-host/src/extension.rs index a545f71..bc2969a 100644 --- a/orchid-host/src/extension.rs +++ b/orchid-host/src/extension.rs @@ -28,6 +28,7 @@ use crate::api; use crate::atom::AtomHand; use crate::ctx::Ctx; use crate::dealias::{ChildError, ChildErrorKind, walk}; +use crate::expr::ExprKind; use crate::expr_store::ExprStore; use crate::system::SystemCtor; use crate::tree::MemberKind; @@ -56,6 +57,7 @@ impl Drop for ExtensionData { let mut exiting_snd = self.exiting_snd.clone(); (self.ctx.spawn)(Box::pin(async move { reqnot.notify(api::HostExtNotif::Exit).await; + exiting_snd.send(()).await.unwrap() })) } @@ -247,6 +249,13 @@ impl Extension { let unit = atom.print(&FmtCtxImpl { i: &this.ctx().i }).await; hand.handle(eap, &unit.to_api()).await }, + api::ExtHostReq::CreateAtom(ref create @ api::CreateAtom(ref atom, target)) => { + let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await; + let target = ctx.system_inst(target).await.expect("Invalid recipient for atom"); + let expr = ExprKind::Atom(atom).at(Pos::None); + target.ext().exprs().give_expr(expr.clone()); + hand.handle(create, &expr.id()).await + }, } }) } diff --git a/orchid-host/src/parse.rs b/orchid-host/src/parse.rs index 92d6c79..87a8bdb 100644 --- a/orchid-host/src/parse.rs +++ b/orchid-host/src/parse.rs @@ -1,4 +1,4 @@ -use futures::future::join_all; +use futures::FutureExt; use itertools::Itertools; use orchid_base::error::{OrcRes, Reporter, mk_errv}; use orchid_base::format::fmt; @@ -51,9 +51,14 @@ pub async fn parse_items( items: ParsSnippet<'_>, ) -> OrcRes> { let lines = line_items(ctx, items).await; - let line_res = - join_all(lines.into_iter().map(|p| parse_item(ctx, path.clone(), p.output, p.tail))).await; - Ok(line_res.into_iter().flat_map(|l| l.ok().into_iter().flatten()).collect()) + let mut line_ok = Vec::new(); + for Parsed { output: comments, tail } in lines { + match parse_item(ctx, path.clone(), comments, tail).boxed_local().await { + Err(e) => ctx.rep().report(e), + Ok(l) => line_ok.extend(l), + } + } + Ok(line_ok) } pub async fn parse_item( diff --git a/orchid-host/src/sys_parser.rs b/orchid-host/src/sys_parser.rs index 4022e94..f4953be 100644 --- a/orchid-host/src/sys_parser.rs +++ b/orchid-host/src/sys_parser.rs @@ -93,13 +93,14 @@ async fn conv( }, }; let name = ctx.i.ex(name).await; + let mem_path = module.push(name.clone()); let mkind = match kind { api::ParsedMemberKind::Module { lines, use_prelude } => { - let items = conv(lines, module.push(name.clone()), callback, ctx).boxed_local().await?; + let items = conv(lines, mem_path, callback, ctx).boxed_local().await?; ParsedMemberKind::Mod(ParsedModule::new(use_prelude, items)) }, api::ParsedMemberKind::Constant(cid) => { - ctx.sys.0.const_paths.insert(cid, ctx.mod_path.suffix(module.unreverse(), ctx.i).await); + ctx.sys.0.const_paths.insert(cid, ctx.mod_path.suffix(mem_path.unreverse(), ctx.i).await); ParsedMemberKind::Const(cid, ctx.sys.clone()) }, }; diff --git a/orchid-host/src/system.rs b/orchid-host/src/system.rs index c91cc84..6dad18f 100644 --- a/orchid-host/src/system.rs +++ b/orchid-host/src/system.rs @@ -3,9 +3,9 @@ use std::fmt; use std::future::Future; use std::rc::{Rc, Weak}; -use async_lock::RwLock; use derive_destructure::destructure; use futures::future::join_all; +use futures_locks::RwLock; use hashbrown::HashMap; use itertools::Itertools; use memo_map::MemoMap; @@ -163,6 +163,9 @@ impl System { )), None => (), } + if root_data.root.members.get(selector).is_some() { + return Ok(VName::new(rel.iter().cloned()).expect("split_first was called above")); + } if tail.is_empty() { return Ok(VPath::new(cwd.iter().cloned()).name_with_suffix(selector.clone())); } diff --git a/orchid-host/src/tree.rs b/orchid-host/src/tree.rs index d13a2b1..a496bcb 100644 --- a/orchid-host/src/tree.rs +++ b/orchid-host/src/tree.rs @@ -4,10 +4,10 @@ use std::cell::RefCell; use std::rc::{Rc, Weak}; use std::slice; -use async_lock::RwLock; use async_once_cell::OnceCell; use derive_destructure::destructure; use futures::{FutureExt, StreamExt, stream}; +use futures_locks::RwLock; use hashbrown::HashMap; use hashbrown::hash_map::Entry; use itertools::Itertools; diff --git a/orchid-std/Cargo.toml b/orchid-std/Cargo.toml index e2723b9..f56e74f 100644 --- a/orchid-std/Cargo.toml +++ b/orchid-std/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" } async-once-cell = "0.5.4" futures = { version = "0.3.31", features = ["std"], default-features = false } hashbrown = "0.16.0" @@ -15,10 +16,10 @@ orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" } orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" } orchid-base = { version = "0.1.0", path = "../orchid-base" } orchid-extension = { version = "0.1.0", path = "../orchid-extension", features = [ - "tokio", + "tokio", ] } ordered-float = "5.0.0" -rust_decimal = "1.37.2" +rust_decimal = "1.38.0" substack = "1.1.1" tokio = { version = "1.47.1", features = ["full"] } diff --git a/orchid-std/src/lib.rs b/orchid-std/src/lib.rs index 15185be..495ee80 100644 --- a/orchid-std/src/lib.rs +++ b/orchid-std/src/lib.rs @@ -7,3 +7,4 @@ pub use std::string::str_atom::OrcString; pub use macros::macro_system::MacroSystem; pub use macros::mactree::{MacTok, MacTree}; +use orchid_api as api; diff --git a/orchid-std/src/macros/instantiate_tpl.rs b/orchid-std/src/macros/instantiate_tpl.rs index 79cf9a7..5693ece 100644 --- a/orchid-std/src/macros/instantiate_tpl.rs +++ b/orchid-std/src/macros/instantiate_tpl.rs @@ -1,9 +1,11 @@ use std::borrow::Cow; use never::Never; -use orchid_extension::atom::{Atomic, TypAtom}; +use orchid_base::format::fmt; +use orchid_extension::atom::{Atomic, TAtom}; use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own}; -use orchid_extension::conv::{ToExpr, TryFromExpr}; +use orchid_extension::conv::ToExpr; +use orchid_extension::coroutine_exec::exec; use orchid_extension::expr::Expr; use orchid_extension::gen_expr::GExpr; @@ -24,26 +26,33 @@ impl OwnedAtom for InstantiateTplCall { type Refs = Never; // Technically must be supported but shouldn't actually ever be called async fn call_ref(&self, arg: Expr) -> GExpr { - eprintln!( - "Copying partially applied instantiate_tpl call. This is an internal value.\ - \nIt should be fully consumed within generated code." - ); + if !self.argv.is_empty() { + eprintln!( + "Copying partially applied instantiate_tpl call. This is an internal value.\ + \nIt should be fully consumed within generated code." + ); + } self.clone().call(arg).await } async fn call(mut self, arg: Expr) -> GExpr { - match TypAtom::::try_from_expr(arg).await { - Err(e) => return Err::(e).to_expr().await, - Ok(t) => self.argv.push(own(t).await), - }; - if self.argv.len() < self.argc { - return self.to_expr().await; - } - let mut args = self.argv.into_iter(); - let ret = map_mactree(&self.tpl, &mut false, &mut |mt| match mt.tok() { - MacTok::Slot => Some(args.next().expect("Not enough arguments to fill all slots")), - _ => None, - }); - assert!(args.next().is_none(), "Too many arguments for all slots"); - ret.to_expr().await + exec("macros::instantiate_tpl", async move |mut h| { + match h.exec::>(arg.clone()).await { + Err(_) => panic!("Expected a macro param, found {}", fmt(&arg, arg.ctx().i()).await), + Ok(t) => self.argv.push(own(t).await), + }; + if self.argv.len() < self.argc { + return self.to_expr().await; + } + let mut args = self.argv.into_iter(); + let ret = map_mactree(&self.tpl, &mut false, &mut |mt| match mt.tok() { + MacTok::Slot => Some(args.next().expect("Not enough arguments to fill all slots")), + _ => None, + }); + assert!(args.next().is_none(), "Too many arguments for all slots"); + ret.to_expr().await + }) + .await + .to_expr() + .await } } diff --git a/orchid-std/src/macros/let_line.rs b/orchid-std/src/macros/let_line.rs index d0d884c..d99bf99 100644 --- a/orchid-std/src/macros/let_line.rs +++ b/orchid-std/src/macros/let_line.rs @@ -10,10 +10,13 @@ use orchid_base::parse::{ }; use orchid_base::sym; use orchid_base::tree::Paren; +use orchid_extension::atom::TAtom; +use orchid_extension::conv::TryFromExpr; use orchid_extension::gen_expr::{atom, call, sym_ref}; use orchid_extension::parser::{ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser}; use crate::macros::mactree::{MacTok, MacTree, glossary_v, map_mactree_v}; +use crate::macros::ph_lexer::PhAtom; #[derive(Default)] pub struct LetLine; @@ -42,10 +45,10 @@ impl Parser for LetLine { if let Some(e) = rep.errv() { return Err(e); } - Ok(call([ - sym_ref(sym!(macros::lower; ctx.i()).await), - call([sym_ref(sym!(macros::resolve; ctx.i()).await), atom(macro_input)]), - ])) + Ok(call(sym_ref(sym!(macros::lower; ctx.i()).await), [call( + sym_ref(sym!(macros::resolve; ctx.i()).await), + [atom(macro_input)], + )])) })]) } } @@ -110,7 +113,10 @@ pub async fn parse_tok(tree: &PTokTree, ctx: &impl ParseCtx) -> Option return parse_tok(nested, ctx).boxed_local().await; }, }, - PTok::Handle(expr) => MacTok::Value(expr.clone()), + PTok::Handle(expr) => match TAtom::::try_from_expr(expr.clone()).await { + Err(_) => MacTok::Value(expr.clone()), + Ok(ta) => MacTok::Ph(ta.value.to_full(ta.ctx()).await), + }, PTok::NewExpr(never) => match *never {}, PTok::LambdaHead(_) => panic!("Lambda-head handled in the sequence parser"), PTok::S(p, body) => diff --git a/orchid-std/src/macros/macro_lib.rs b/orchid-std/src/macros/macro_lib.rs index e2507bd..865576e 100644 --- a/orchid-std/src/macros/macro_lib.rs +++ b/orchid-std/src/macros/macro_lib.rs @@ -1,58 +1,47 @@ use hashbrown::HashMap; -use itertools::Itertools; +use itertools::{Itertools, chain}; use orchid_base::error::Reporter; -use orchid_base::sym; -use orchid_extension::atom::TypAtom; +use orchid_base::{clone, sym}; +use orchid_extension::atom::TAtom; use orchid_extension::atom_owned::own; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::exec; -use orchid_extension::gen_expr::{atom, call, sym_ref}; +use orchid_extension::func_atom::Lambda; +use orchid_extension::gen_expr::{call, sym_ref}; use orchid_extension::reflection::{ReflMemKind, refl}; -use orchid_extension::tree::{GenMember, comments, fun, prefix}; +use orchid_extension::tree::{GenMember, MemKind, fun, lazy, prefix}; use substack::Substack; -use crate::Int; -use crate::macros::instantiate_tpl::InstantiateTplCall; -use crate::macros::macro_line::{Macro, Matcher}; -use crate::macros::mactree::{LowerCtx, MacTree}; -use crate::macros::recur_state::RecurState; +use crate::MacTok; +use crate::macros::macro_value::{Macro, Matcher}; +use crate::macros::mactree::{LowerCtx, MacTree, Ph}; use crate::macros::resolve::{ResolveCtx, resolve}; +use crate::macros::utils::{mactree, mactreev, mk_macro}; pub fn gen_macro_lib() -> Vec { prefix("macros", [ - comments( - ["This is an internal function, you can't obtain a value of its argument type.", "hidden"], - fun(true, "instantiate_tpl", |tpl: TypAtom, right: Int| async move { - InstantiateTplCall { - tpl: own(tpl).await, - argc: right.0.try_into().unwrap(), - argv: Vec::new(), - } - }), - ), - fun(true, "resolve", |tpl: TypAtom| async move { - call([ - sym_ref(sym!(macros::resolve_recur; tpl.untyped.ctx().i()).await), - atom(RecurState::Bottom), - tpl.untyped.ex().to_expr().await, - ]) - }), - fun(true, "lower", |tpl: TypAtom| async move { + fun(true, "lower", |tpl: TAtom| async move { let ctx = LowerCtx { sys: tpl.untyped.ctx().clone(), rep: &Reporter::new() }; let res = own(tpl).await.lower(ctx, Substack::Bottom).await; if let Some(e) = Reporter::new().errv() { Err(e) } else { Ok(res) } }), - fun(true, "resolve_recur", |state: TypAtom, tpl: TypAtom| async move { - exec("macros::resolve_recur", async move |mut h| { + fun(true, "recur", async |tpl: TAtom| { + call(sym_ref(sym!(macros::lower; tpl.i()).await), [call( + sym_ref(sym!(macros::resolve; tpl.i()).await), + [tpl.to_expr().await], + )]) + }), + fun(true, "resolve", |tpl: TAtom| async move { + exec("macros::resolve", async move |mut h| { let ctx = tpl.ctx().clone(); let root = refl(&ctx); let tpl = own(tpl.clone()).await; let mut macros = HashMap::new(); for n in tpl.glossary() { if let Ok(ReflMemKind::Const) = root.get_by_path(n).await.map(|m| m.kind()) { - let Ok(mac) = h.exec::>(sym_ref(n.clone())).await else { continue }; + let Ok(mac) = h.exec::>(sym_ref(n.clone())).await else { continue }; let mac = own(mac).await; - macros.entry(mac.0.own_kws[0].clone()).or_insert(mac); + macros.entry(mac.canonical_name(&ctx).await).or_insert(mac); } } let mut named = HashMap::new(); @@ -69,7 +58,7 @@ pub fn gen_macro_lib() -> Vec { } } let priod = priod.into_iter().sorted_unstable_by_key(|(p, _)| *p).map(|(_, r)| r).collect(); - let mut rctx = ResolveCtx { h, recur: own(state).await, ctx: ctx.clone(), named, priod }; + let mut rctx = ResolveCtx { h, ctx: ctx.clone(), named, priod }; let resolve_res = resolve(&mut rctx, &tpl).await; std::mem::drop(rctx); match resolve_res { @@ -79,5 +68,30 @@ pub fn gen_macro_lib() -> Vec { }) .await }), + // TODO test whether any of this worked + lazy(true, "common", async |_, ctx| { + let add_macro = { + clone!(ctx); + mk_macro(Some(1), ["+"], [( + mactreev!(ctx.i(); "...$" lhs 0 macros::common::+ "...$" rhs 1), + Lambda::new("std::number::add", async move |lhs: TAtom, rhs: TAtom| { + mactree!(ctx.i(); std::number::add + (macros::recur "'" lhs.ex();) + (macros::recur "'" rhs.ex();) + ) + }), + )]) + }; + let mul_macro = mk_macro(Some(2), ["*"], [( + mactreev!(ctx.i(); "...$" lhs 0 macros::common::* "...$" rhs 1), + Lambda::new("std::number::mul", async |lhs: TAtom, rhs: TAtom| { + mactree!(lhs.ctx().i(); std::number::mul + (macros::recur "'" lhs.ex();) + (macros::recur "'" rhs.ex();) + ) + }), + )]); + MemKind::Mod { members: chain!(add_macro, mul_macro).collect_vec() } + }), ]) } diff --git a/orchid-std/src/macros/macro_line.rs b/orchid-std/src/macros/macro_line.rs index 03dc51a..0895b8d 100644 --- a/orchid-std/src/macros/macro_line.rs +++ b/orchid-std/src/macros/macro_line.rs @@ -1,31 +1,24 @@ -use std::borrow::Cow; use std::cell::RefCell; use std::rc::Rc; use async_once_cell::OnceCell; use futures::{StreamExt, stream}; -use hashbrown::{HashMap, HashSet}; use itertools::Itertools; -use never::Never; use orchid_base::error::{OrcRes, Reporter, mk_errv}; -use orchid_base::interner::Tok; -use orchid_base::location::Pos; -use orchid_base::name::Sym; use orchid_base::parse::{ Comment, ParseCtx, Parsed, Snippet, expect_end, expect_tok, line_items, token_errv, try_pop_no_fluff, }; use orchid_base::tree::{Paren, Token}; use orchid_base::{clone, sym}; -use orchid_extension::atom::{Atomic, TypAtom}; -use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant}; +use orchid_extension::atom::TAtom; use orchid_extension::conv::{ToExpr, TryFromExpr}; use orchid_extension::gen_expr::{atom, call, sym_ref}; use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser}; use crate::macros::let_line::{dealias_mac_v, parse_tokv}; +use crate::macros::macro_value::{Macro, MacroData, Matcher, Rule}; use crate::macros::mactree::{glossary_v, map_mactree_v}; -use crate::macros::recur_state::{RecurState, RulePath}; use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher}; use crate::{Int, MacTok}; @@ -48,42 +41,46 @@ impl Parser for MacroLine { )); } let module = ctx.module(); - let Parsed { output, tail } = try_pop_no_fluff(&ctx, line).await?; + let Parsed { output: prio_or_body, tail } = try_pop_no_fluff(&ctx, line).await?; let bad_first_item_err = || { - token_errv(&ctx, output, "Expected priority or block", |s| { + token_errv(&ctx, prio_or_body, "Expected priority or block", |s| { format!("Expected a priority number or a () block, found {s}") }) }; - let (prio, body) = match &output.tok { - Token::S(Paren::Round, body) => (None, body), - Token::Handle(expr) => match TypAtom::::try_from_expr(expr.clone()).await { + let (prio, body) = match &prio_or_body.tok { + Token::S(Paren::Round, body) => { + expect_end(&ctx, tail).await?; + (None, body) + }, + Token::Handle(expr) => match TAtom::::try_from_expr(expr.clone()).await { Err(e) => { return Err(e + bad_first_item_err().await); }, Ok(prio) => { - let Token::S(Paren::Round, block) = &output.tok else { + let Parsed { output: body, tail } = try_pop_no_fluff(&ctx, tail).await?; + let Token::S(Paren::Round, block) = &body.tok else { return Err( - token_errv(&ctx, output, "Expected () block", |s| { + token_errv(&ctx, prio_or_body, "Expected () block", |s| { format!("Expected a () block, found {s}") }) .await, ); }; + expect_end(&ctx, tail).await?; (Some(prio), block) }, }, _ => return Err(bad_first_item_err().await), }; - expect_end(&ctx, tail).await?; - let lines = line_items(&ctx, Snippet::new(output, body)).await; + let lines = line_items(&ctx, Snippet::new(prio_or_body, body)).await; let Some((kw_line, rule_lines)) = lines.split_first() else { return Ok(Vec::new()) }; - let mut keywords = HashMap::new(); + let mut keywords = Vec::new(); let Parsed { tail: kw_tail, .. } = expect_tok(&ctx, kw_line.tail, ctx.i().i("keywords").await).await?; for kw_tok in kw_tail.iter().filter(|kw| !kw.is_fluff()) { match kw_tok.as_name() { Some(kw) => { - keywords.insert(kw, kw_tok.sr()); + keywords.push((kw, kw_tok.sr())); }, None => ctx.rep().report( token_errv(&ctx, kw_tok, "invalid macro keywords list", |tok| { @@ -93,7 +90,7 @@ impl Parser for MacroLine { ), } } - let Some(macro_name) = keywords.keys().next().cloned() else { + let Some((macro_name, _)) = keywords.first().cloned() else { return Err(mk_errv( ctx.i().i("macro with no keywords").await, "Macros must define at least one macro of their own.", @@ -103,9 +100,8 @@ impl Parser for MacroLine { let mut rules = Vec::new(); let mut lines = Vec::new(); for (idx, line) in rule_lines.iter().enumerate().map(|(n, v)| (n as u32, v)) { - let path = RulePath { module: module.clone(), main_kw: macro_name.clone(), rule: idx }; let sr = line.tail.sr(); - let name = ctx.i().i(&path.name()).await; + let name = ctx.i().i(&format!("rule::{}::{}", macro_name, idx)).await; let Parsed { tail, .. } = expect_tok(&ctx, line.tail, ctx.i().i("rule").await).await?; let arrow_token = ctx.i().i("=>").await; let Some((pattern, body)) = tail.split_once(|tok| tok.is_kw(arrow_token.clone())) else { @@ -132,7 +128,7 @@ impl Parser for MacroLine { ] } let body_sr = body.sr(); - rules.push((name.clone(), placeholders, rules.len() as u32, sr.pos(), pattern)); + rules.push((name.clone(), placeholders, pattern)); lines.push(ParsedLine::cnst(&sr, &line.output, true, name, async move |ctx| { let rep = Reporter::new(); let body = dealias_mac_v(body_mactree, &ctx, &rep).await; @@ -140,25 +136,23 @@ impl Parser for MacroLine { if let Some(e) = rep.errv() { return Err(e); } - Ok(call([ - sym_ref(sym!(macros::resolve_recur; ctx.i()).await), - atom(RecurState::base(path)), - macro_input.to_expr().await, - ])) + Ok(call(sym_ref(sym!(macros::lower; ctx.i()).await), [call( + sym_ref(sym!(macros::resolve; ctx.i()).await), + [macro_input.to_expr().await], + )])) })) } let mac_cell = Rc::new(OnceCell::new()); - let keywords = Rc::new(keywords); let rules = Rc::new(RefCell::new(Some(rules))); for (kw, sr) in &*keywords { - clone!(mac_cell, keywords, rules, module, prio); + clone!(mac_cell, rules, module, prio); lines.push(ParsedLine::cnst(&sr.clone(), &comments, true, kw.clone(), async move |cctx| { let mac = mac_cell .get_or_init(async { let rep = Reporter::new(); let rules = rules.borrow_mut().take().expect("once cell initializer runs"); let rules = stream::iter(rules) - .then(|(body_name, placeholders, index, pos, pattern_macv)| { + .then(|(body_name, placeholders, pattern_macv)| { let cctx = &cctx; let rep = &rep; let prio = &prio; @@ -171,8 +165,7 @@ impl Parser for MacroLine { }; let placeholders = placeholders.into_iter().map(|(ph, _)| ph.name).collect_vec(); match pattern_res { - Ok(pattern) => - Some(Rule { index, pos, body_name, pattern, glossary, placeholders }), + Ok(pattern) => Some(Rule { body_name, pattern, glossary, placeholders }), Err(e) => { rep.report(e); None @@ -183,8 +176,7 @@ impl Parser for MacroLine { .flat_map(stream::iter) .collect::>() .await; - let own_kws = keywords.keys().cloned().collect_vec(); - Macro(Rc::new(MacroData { module, prio: prio.map(|i| i.0 as u64), rules, own_kws })) + Macro(Rc::new(MacroData { module, prio: prio.map(|i| i.0 as u64), rules })) }) .await; atom(mac.clone()) @@ -193,36 +185,3 @@ impl Parser for MacroLine { Ok(lines) } } - -#[derive(Debug)] -pub struct MacroData { - pub module: Sym, - pub prio: Option, - pub rules: Vec, - pub own_kws: Vec>, -} - -#[derive(Clone, Debug)] -pub struct Macro(pub Rc); -#[derive(Debug)] -pub struct Rule { - pub index: u32, - pub pos: Pos, - pub pattern: Matcher, - pub glossary: HashSet, - pub placeholders: Vec>, - pub body_name: Tok, -} -#[derive(Debug)] -pub enum Matcher { - Named(NamedMatcher), - Priod(PriodMatcher), -} -impl Atomic for Macro { - type Data = (); - type Variant = OwnedVariant; -} -impl OwnedAtom for Macro { - type Refs = Never; - async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } -} diff --git a/orchid-std/src/macros/macro_system.rs b/orchid-std/src/macros/macro_system.rs index a3b7dd8..e070ef1 100644 --- a/orchid-std/src/macros/macro_system.rs +++ b/orchid-std/src/macros/macro_system.rs @@ -1,7 +1,7 @@ -use never::Never; use orchid_base::interner::Interner; use orchid_base::name::Sym; use orchid_base::reqnot::Receipt; +use orchid_base::sym; use orchid_extension::atom::{AtomDynfo, AtomicFeatures}; use orchid_extension::entrypoint::ExtReq; use orchid_extension::lexer::LexerObj; @@ -14,9 +14,11 @@ use orchid_extension::tree::GenMember; use crate::macros::instantiate_tpl::InstantiateTplCall; use crate::macros::let_line::LetLine; use crate::macros::macro_lib::gen_macro_lib; -use crate::macros::macro_line::{Macro, MacroLine}; +use crate::macros::macro_line::MacroLine; +use crate::macros::macro_value::Macro; use crate::macros::mactree_lexer::MacTreeLexer; -use crate::macros::recur_state::RecurState; +use crate::macros::ph_lexer::{PhAtom, PhLexer}; +use crate::macros::requests::MacroReq; use crate::{MacTree, StdSystem}; #[derive(Default)] @@ -30,20 +32,26 @@ impl SystemCtor for MacroSystem { } impl SystemCard for MacroSystem { type Ctor = Self; - type Req = Never; + type Req = MacroReq; fn atoms() -> impl IntoIterator>> { [ Some(InstantiateTplCall::dynfo()), Some(MacTree::dynfo()), - Some(RecurState::dynfo()), Some(Macro::dynfo()), + Some(PhAtom::dynfo()), ] } } impl System for MacroSystem { - async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { match req {} } - async fn prelude(_: &Interner) -> Vec { vec![] } - fn lexers() -> Vec { vec![&MacTreeLexer] } + async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { todo!("Handle {req:?}") } + async fn prelude(i: &Interner) -> Vec { + vec![ + sym!(macros::resolve; i).await, + sym!(macros::common::+; i).await, + sym!(macros::common::*; i).await, + ] + } + fn lexers() -> Vec { vec![&MacTreeLexer, &PhLexer] } fn parsers() -> Vec { vec![&LetLine, &MacroLine] } fn env() -> Vec { gen_macro_lib() } } diff --git a/orchid-std/src/macros/macro_value.rs b/orchid-std/src/macros/macro_value.rs new file mode 100644 index 0000000..014efce --- /dev/null +++ b/orchid-std/src/macros/macro_value.rs @@ -0,0 +1,48 @@ +use std::borrow::Cow; +use std::rc::Rc; + +use hashbrown::HashSet; +use never::Never; +use orchid_base::interner::Tok; +use orchid_base::name::Sym; +use orchid_extension::atom::Atomic; +use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant}; +use orchid_extension::system::SysCtx; + +use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher}; + +#[derive(Debug)] +pub struct MacroData { + pub module: Sym, + pub prio: Option, + pub rules: Vec, +} + +#[derive(Clone, Debug)] +pub struct Macro(pub Rc); +impl Macro { + pub async fn canonical_name(&self, ctx: &SysCtx) -> Sym { + self.0.module.suffix([self.0.rules[0].body_name.clone()], ctx.i()).await + } +} + +#[derive(Debug)] +pub struct Rule { + pub pattern: Matcher, + pub glossary: HashSet, + pub placeholders: Vec>, + pub body_name: Tok, +} +#[derive(Debug)] +pub enum Matcher { + Named(NamedMatcher), + Priod(PriodMatcher), +} +impl Atomic for Macro { + type Data = (); + type Variant = OwnedVariant; +} +impl OwnedAtom for Macro { + type Refs = Never; + async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } +} diff --git a/orchid-std/src/macros/mactree.rs b/orchid-std/src/macros/mactree.rs index 519c5ab..ce90f06 100644 --- a/orchid-std/src/macros/mactree.rs +++ b/orchid-std/src/macros/mactree.rs @@ -6,6 +6,7 @@ use futures::FutureExt; use futures::future::join_all; use hashbrown::HashSet; use itertools::Itertools; +use orchid_api_derive::Coding; use orchid_base::error::{OrcErrv, Reporter, mk_errv}; use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants, fmt}; use orchid_base::interner::Tok; @@ -42,38 +43,56 @@ impl MacTree { MacTok::Bottom(e) => bot(e.clone()), MacTok::Lambda(arg, body) => { let MacTok::Name(name) = &*arg.tok else { - let err = mk_errv( + return bot(mk_errv( ctx.sys.i().i("Syntax error after macros").await, "This token ends up as a binding, consider replacing it with a name", [arg.pos()], - ); - ctx.rep.report(err.clone()); - return bot(err); + )); }; - lambda(args.len() as u64, lower_v(body, ctx, args.push(name.clone())).await) + let arg_pos = args.len() as u64; + let args = args.push(name.clone()); + let body = match &body[..] { + [] => bot(mk_errv( + ctx.sys.i().i("Empty lambda body").await, + "Lambdas must evaluate to an expression", + [self.pos()], + )), + [f, argv @ ..] => call( + f.lower(ctx.clone(), args.clone()).boxed_local().await, + lower_v(argv, ctx, args).await, + ), + }; + lambda(arg_pos, body) }, MacTok::Name(name) => match args.iter().enumerate().find(|(_, n)| *n == name) { None => sym_ref(name.clone()), - Some((i, _)) => arg((args.len() - i) as u64), + Some((i, _)) => arg((args.len() - i - 1) as u64), }, MacTok::Ph(ph) => { - let err = mk_errv( + return bot(mk_errv( ctx.sys.i().i("Placeholder in value").await, format!("Placeholder {ph} is only supported in macro patterns"), [self.pos()], - ); - ctx.rep.report(err.clone()); - return bot(err); + )); + }, + MacTok::S(Paren::Round, body) => match &body[..] { + [fun, argv @ ..] => call( + fun.lower(ctx.clone(), args.clone()).boxed_local().await, + lower_v(argv, ctx, args).await, + ), + [] => + return bot(mk_errv( + ctx.sys.i().i("Empty ()").await, + "Empty () is not a meaningful expression", + [self.pos()], + )), }, - MacTok::S(Paren::Round, body) => call(lower_v(body, ctx, args).await), MacTok::S(..) => { - let err = mk_errv( + return bot(mk_errv( ctx.sys.i().i("[] or {} after macros").await, format!("{} didn't match any macro", fmt(self, ctx.sys.i()).await), [self.pos()], - ); - ctx.rep.report(err.clone()); - return bot(err); + )); }, MacTok::Slot => panic!("Uninstantiated template should never be exposed"), MacTok::Value(v) => v.clone().to_expr().await, @@ -90,7 +109,8 @@ impl OwnedAtom for MacTree { async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } async fn print_atom<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { - self.tok.print(c).await + tl_cache!(Rc: Rc::new(Variants::default().bounded("'{0}"))) + .units([self.tok.print(c).await]) } } impl Format for MacTree { @@ -134,22 +154,18 @@ 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::Lambda(arg, b) => FmtUnit::new( - tl_cache!(Rc: Rc::new(Variants::default() - .unbounded("\\{0b}.{1l}") - .bounded("(\\{0b}.{1b})"))), - [arg.print(c).boxed_local().await, mtreev_fmt(b, c).await], - ), + Self::Lambda(arg, b) => tl_cache!(Rc: Rc::new(Variants::default() + .unbounded("\\{0} {1l}") + .bounded("(\\{0} {1b})"))) + .units([arg.print(c).boxed_local().await, mtreev_fmt(b, c).await]), Self::Name(n) => format!("{n}").into(), Self::Ph(ph) => format!("{ph}").into(), - Self::S(p, body) => FmtUnit::new( - match *p { - Paren::Round => Rc::new(Variants::default().bounded("({0b})")), - Paren::Curly => Rc::new(Variants::default().bounded("{{0b}}")), - Paren::Square => Rc::new(Variants::default().bounded("[{0b}]")), - }, - [mtreev_fmt(body, c).await], - ), + Self::S(p, body) => match *p { + Paren::Round => tl_cache!(Rc: Rc::new(Variants::default().bounded("({0b})"))), + Paren::Curly => tl_cache!(Rc: Rc::new(Variants::default().bounded("{{0b}}"))), + Paren::Square => tl_cache!(Rc: Rc::new(Variants::default().bounded("[{0b}]"))), + } + .units([mtreev_fmt(body, c).await]), Self::Slot => "$SLOT".into(), Self::Bottom(err) if err.len() == 1 => format!("Bottom({}) ", err.one().unwrap()).into(), Self::Bottom(err) => format!("Botttom(\n{}) ", indent(&err.to_string())).into(), @@ -181,7 +197,7 @@ impl Display for Ph { } } -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Coding)] pub enum PhKind { Scalar, Vector { at_least_one: bool, priority: u8 }, diff --git a/orchid-std/src/macros/mactree_lexer.rs b/orchid-std/src/macros/mactree_lexer.rs index 7de0dda..11da296 100644 --- a/orchid-std/src/macros/mactree_lexer.rs +++ b/orchid-std/src/macros/mactree_lexer.rs @@ -1,15 +1,16 @@ use std::ops::RangeInclusive; use futures::FutureExt; +use itertools::chain; use orchid_base::error::{OrcRes, mk_errv}; use orchid_base::parse::ParseCtx; -use orchid_base::sym; use orchid_base::tokens::PARENS; use orchid_base::tree::Paren; use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; use orchid_extension::parser::p_tree2gen; -use orchid_extension::tree::{GenTok, GenTokTree, ref_tok, x_tok}; +use orchid_extension::tree::{GenTok, GenTokTree, x_tok}; +use crate::macros::instantiate_tpl::InstantiateTplCall; use crate::macros::let_line::parse_tok; use crate::macros::mactree::{MacTok, MacTree}; @@ -29,12 +30,9 @@ impl Lexer for MacTreeLexer { let tok = match &args[..] { [] => x_tok(mactree).await, _ => { - let call = ([ - ref_tok(sym!(macros::instantiate_tpl; ctx.i()).await).await.at(range.clone()), - x_tok(mactree).await.at(range.clone()), - ] - .into_iter()) - .chain(args.into_iter()); + let instantiate_tpl_call = + InstantiateTplCall { argc: args.len(), argv: vec![], tpl: mactree }; + let call = chain!([x_tok(instantiate_tpl_call).await.at(range.clone())], args); GenTok::S(Paren::Round, call.collect()) }, }; diff --git a/orchid-std/src/macros/mod.rs b/orchid-std/src/macros/mod.rs index 7a1579c..e94927a 100644 --- a/orchid-std/src/macros/mod.rs +++ b/orchid-std/src/macros/mod.rs @@ -3,10 +3,13 @@ mod let_line; mod macro_lib; mod macro_line; pub mod macro_system; +mod macro_value; pub mod mactree; mod mactree_lexer; -pub mod recur_state; +mod ph_lexer; +mod requests; mod resolve; mod rule; +mod utils; use mactree::{MacTok, MacTree}; diff --git a/orchid-std/src/macros/ph_lexer.rs b/orchid-std/src/macros/ph_lexer.rs new file mode 100644 index 0000000..41167c6 --- /dev/null +++ b/orchid-std/src/macros/ph_lexer.rs @@ -0,0 +1,79 @@ +use orchid_api_derive::Coding; +use orchid_base::error::{OrcRes, mk_errv}; +use orchid_base::format::FmtUnit; +use orchid_base::parse::{name_char, name_start}; +use orchid_extension::atom::Atomic; +use orchid_extension::atom_thin::{ThinAtom, ThinVariant}; +use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable}; +use orchid_extension::system::SysCtx; +use orchid_extension::tree::{GenTokTree, x_tok}; + +use crate::macros::mactree::{Ph, PhKind}; + +#[derive(Clone, Coding)] +pub struct PhAtom(orchid_api::TStr, PhKind); +impl PhAtom { + pub async fn to_full(&self, ctx: &SysCtx) -> Ph { + Ph { kind: self.1, name: ctx.i().ex(self.0).await } + } +} +impl Atomic for PhAtom { + type Data = Self; + type Variant = ThinVariant; +} +impl ThinAtom for PhAtom { + async fn print(&self, ctx: SysCtx) -> FmtUnit { + Ph { name: ctx.i().ex(self.0).await, kind: self.1 }.to_string().into() + } +} + +#[derive(Default)] +pub struct PhLexer; +impl Lexer for PhLexer { + const CHAR_FILTER: &'static [std::ops::RangeInclusive] = &['$'..='$', '.'..='.']; + async fn lex<'a>(line: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> { + let (tail, name, phkind) = if let Some(tail) = line.strip_prefix("$") + && tail.starts_with(name_start) + { + let name = tail.split_once(|c| !name_char(c)).map_or("", |(h, _)| h); + let tail = tail.split_at(name.len()).1; + (tail, name, PhKind::Scalar) + } else { + async fn name_and_prio<'a>( + tail: &'a str, + ctx: &'a LexContext<'a>, + ) -> OrcRes<(&'a str, u8, &'a str)> { + let name = tail.split_once(|c| !name_char(c)).map_or("", |(h, _)| h); + let tail = tail.split_at(name.len()).1; + let (prio, tail) = match tail.strip_prefix(":") { + None => (0, tail), + Some(tail) => { + let prio = tail.split_once(|c: char| c.is_ascii_digit()).map_or("", |(h, _)| h); + let tail = tail.split_at(prio.len()).1; + if let Ok(prio_num) = prio.parse::() { + (prio_num, tail) + } else { + return Err(mk_errv( + ctx.ctx.i().i("Invalid priority, must be 0-255").await, + format!("{prio} is not a valid placeholder priority"), + [ctx.pos_lt(prio.len(), tail)], + )); + } + }, + }; + Ok((name, prio, tail)) + } + if let Some(tail) = line.strip_prefix("..$") { + let (name, priority, tail) = name_and_prio(tail, ctx).await?; + (tail, name, PhKind::Vector { at_least_one: false, priority }) + } else if let Some(tail) = line.strip_prefix("...$") { + let (name, priority, tail) = name_and_prio(tail, ctx).await?; + (tail, name, PhKind::Vector { at_least_one: true, priority }) + } else { + return Err(err_not_applicable(ctx.ctx.i()).await); + } + }; + let ph_atom = PhAtom(ctx.ctx.i().i::(name).await.to_api(), phkind); + Ok((tail, x_tok(ph_atom).await.at(ctx.pos_tt(line, tail)))) + } +} diff --git a/orchid-std/src/macros/recur_state.rs b/orchid-std/src/macros/recur_state.rs deleted file mode 100644 index 86f3586..0000000 --- a/orchid-std/src/macros/recur_state.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::borrow::Cow; -use std::fmt; -use std::rc::Rc; - -use never::Never; -use orchid_base::format::{FmtCtx, FmtUnit}; -use orchid_base::interner::Tok; -use orchid_base::name::Sym; -use orchid_extension::atom::Atomic; -use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant}; - -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct RulePath { - pub module: Sym, - pub main_kw: Tok, - pub rule: u32, -} -impl RulePath { - pub fn name(&self) -> String { format!("rule::{}::{}", self.main_kw, self.rule) } -} -impl fmt::Display for RulePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Rule {}::({})::{}", self.module, self.main_kw, self.rule) - } -} - -#[derive(Clone)] -pub enum RecurState { - Bottom, - Recursive { path: RulePath, prev: Rc }, -} -impl RecurState { - pub fn base(path: RulePath) -> Self { - RecurState::Recursive { path, prev: Rc::new(RecurState::Bottom) } - } - pub fn push(&self, new: RulePath) -> Option { - let mut cur = self; - while let Self::Recursive { path, prev } = cur { - if &new == path { - return None; - } - cur = prev; - } - Some(Self::Recursive { path: new, prev: Rc::new(self.clone()) }) - } -} -impl Atomic for RecurState { - type Data = Option<()>; - type Variant = OwnedVariant; -} -impl OwnedAtom for RecurState { - type Refs = Never; - - async fn val(&self) -> Cow<'_, Self::Data> { - Cow::Owned(match self { - Self::Bottom => None, - Self::Recursive { .. } => Some(()), - }) - } - async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit { - self.to_string().into() - } -} -impl fmt::Display for RecurState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Bottom => write!(f, "RecurState::Bottom"), - Self::Recursive { path, prev } => write!(f, "{path}\n{prev}"), - } - } -} diff --git a/orchid-std/src/macros/requests.rs b/orchid-std/src/macros/requests.rs new file mode 100644 index 0000000..2b7e935 --- /dev/null +++ b/orchid-std/src/macros/requests.rs @@ -0,0 +1,67 @@ +use orchid_api_derive::{Coding, Hierarchy}; +use orchid_api_traits::Request; + +use crate::api; +use crate::macros::mactree::PhKind; + +/* TODO: +Create handlers and wrappers for these, probably expose MacTree to other crates. +Define new extension binary to test the request functionality. +*/ + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extendable] +pub enum MacroReq { + CreateMacro(CreateMacro), + CreateQuote(CreateQuote), +} + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(MacroReq)] +pub struct CreateMacro { + pub module: api::TStrv, + pub prio: Option, + pub rules: Vec, +} +impl Request for CreateMacro { + type Response = api::ExprTicket; +} + +#[derive(Clone, Debug, Coding)] +pub struct CreateRule { + pub pattern: Vec, + pub body_name: api::TStr, +} + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(MacroReq)] +pub struct CreateQuote(MsgMacTree); +impl Request for CreateQuote { + type Response = api::ExprTicket; +} + +#[derive(Clone, Debug, Coding)] +pub struct MsgMacTree { + pub tok: MsgMacTok, + pub location: api::Location, +} + +#[derive(Clone, Debug, Coding)] +pub enum MsgMacTok { + S(api::Paren, Vec), + Name(api::TStrv), + /// Only permitted in arguments to `instantiate_tpl` + Slot, + Value(api::ExprTicket), + Lambda(Box, Vec), + /// Only permitted in "pattern" values produced by macro blocks, which are + /// never accessed as variables by usercode + Ph(MsgPh), + Bottom(Vec), +} + +#[derive(Clone, Debug, Coding)] +pub struct MsgPh { + kind: PhKind, + name: api::TStr, +} diff --git a/orchid-std/src/macros/resolve.rs b/orchid-std/src/macros/resolve.rs index 633f9ab..7885c03 100644 --- a/orchid-std/src/macros/resolve.rs +++ b/orchid-std/src/macros/resolve.rs @@ -1,25 +1,22 @@ use futures::FutureExt; use hashbrown::HashMap; use itertools::Itertools; -use orchid_base::error::mk_errv; use orchid_base::location::Pos; use orchid_base::name::Sym; use orchid_base::sym; use orchid_base::tree::Paren; use orchid_extension::conv::ToExpr; use orchid_extension::coroutine_exec::ExecHandle; -use orchid_extension::gen_expr::{GExpr, bot, call, sym_ref}; +use orchid_extension::gen_expr::{GExpr, call, sym_ref}; use orchid_extension::system::SysCtx; -use crate::macros::macro_line::{Macro, Rule}; -use crate::macros::recur_state::{RecurState, RulePath}; +use crate::macros::macro_value::{Macro, Rule}; use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher}; use crate::macros::rule::state::{MatchState, StateEntry}; use crate::{MacTok, MacTree}; pub struct ResolveCtx<'a> { pub ctx: SysCtx, - pub recur: RecurState, pub h: ExecHandle<'a>, pub named: HashMap>, pub priod: Vec<(&'a PriodMatcher, &'a Macro, &'a Rule)>, @@ -52,7 +49,7 @@ pub async fn resolve_seq(ctx: &mut ResolveCtx<'_>, val: &[MacTree]) -> Option, val: &[MacTree]) -> Option, val: &[MacTree]) -> Option, - ctx: &SysCtx, - recur: RecurState, -) -> GExpr { - let rule_path = - RulePath { module: mac.0.module.clone(), main_kw: mac.0.own_kws[0].clone(), rule: rule.index }; - let Some(new_recur) = recur.push(rule_path.clone()) else { - return bot(mk_errv( - ctx.i().i("Circular macro dependency").await, - format!("The definition of {rule_path} is circular"), - [rule.pos.clone()], - )); - }; - let mut call_args = vec![sym_ref(mac.0.module.suffix([rule.body_name.clone()], ctx.i()).await)]; +async fn mk_body_call(mac: &Macro, rule: &Rule, state: &MatchState<'_>, ctx: &SysCtx) -> GExpr { + let mut call_args = vec![]; for name in rule.placeholders.iter() { call_args.push(match state.get(name).expect("Missing state entry for placeholder") { StateEntry::Scalar(scal) => (**scal).clone().to_expr().await, StateEntry::Vec(vec) => MacTok::S(Paren::Round, vec.to_vec()).at(Pos::None).to_expr().await, }); } - call_args - .push(call([sym_ref(sym!(macros::resolve_recur; ctx.i()).await), new_recur.to_expr().await])); - call(call_args) + call(sym_ref(sym!(macros::lower; ctx.i()).await), [call( + sym_ref(mac.0.module.suffix([rule.body_name.clone()], ctx.i()).await), + call_args, + )]) } diff --git a/orchid-std/src/macros/rule/vec_match.rs b/orchid-std/src/macros/rule/vec_match.rs index 2dffd61..df17ec8 100644 --- a/orchid-std/src/macros/rule/vec_match.rs +++ b/orchid-std/src/macros/rule/vec_match.rs @@ -36,7 +36,6 @@ pub fn vec_match<'a>( } None }, - // XXX predict heap space usage and allocation count VecMatcher::Middle { left, left_sep, mid, right_sep, right, key_order } => { if seq.len() < left_sep.len() + right_sep.len() { return None; diff --git a/orchid-std/src/macros/utils.rs b/orchid-std/src/macros/utils.rs new file mode 100644 index 0000000..3372ee3 --- /dev/null +++ b/orchid-std/src/macros/utils.rs @@ -0,0 +1,166 @@ +use std::rc::Rc; + +use async_fn_stream::stream; +use futures::StreamExt; +use itertools::{Itertools, chain}; +use orchid_base::name::{NameLike, Sym, VPath}; +use orchid_extension::conv::ToExpr; +use orchid_extension::gen_expr::sym_ref; +use orchid_extension::tree::{GenMember, MemKind, cnst, lazy}; + +use crate::macros::macro_value::{Macro, MacroData, Matcher, Rule}; +use crate::macros::mactree::map_mactree_v; +use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher}; +use crate::{MacTok, MacTree}; + +pub(crate) fn mk_macro( + prio: Option, + own_kws: impl IntoIterator, + rules: impl IntoIterator, B)>, +) -> Vec { + let own_kws = own_kws.into_iter().collect_vec(); + let name = own_kws[0]; + let (patterns, bodies) = rules.into_iter().unzip::<_, _, Vec>, Vec>(); + let main_const = lazy(true, name, async move |path, ctx| { + let module = (Sym::new(path.split_last_seg().1.iter().cloned(), ctx.i()).await) + .expect("Default macro in global root"); + MemKind::Const( + Macro(Rc::new(MacroData { + module, + prio, + rules: stream(async |mut h| { + for (counter, pat) in patterns.into_iter().enumerate() { + let mut placeholders = Vec::new(); + map_mactree_v(&pat, &mut false, &mut |tt| { + if let MacTok::Ph(ph) = &*tt.tok { + placeholders.push(ph.name.clone()) + } + None + }); + let pattern = match prio { + Some(_) => Matcher::Priod(PriodMatcher::new(&pat, ctx.i()).await.unwrap()), + None => Matcher::Named(NamedMatcher::new(&pat, ctx.i()).await.unwrap()), + }; + h.emit(Rule { + glossary: pat.iter().flat_map(|t| t.glossary()).cloned().collect(), + pattern, + placeholders, + body_name: ctx.i().i(&format!("({name})::{counter}")).await, + }) + .await; + } + }) + .collect() + .await, + })) + .to_expr() + .await, + ) + }); + let kw_consts = own_kws[1..].iter().flat_map(|kw| { + lazy(true, kw, async |path, ctx| { + let name = VPath::new(path.split_last_seg().1.iter().cloned()) + .name_with_suffix(ctx.i().i(*kw).await) + .to_sym(ctx.i()) + .await; + MemKind::Const(sym_ref(name)) + }) + }); + let body_consts = (bodies.into_iter().enumerate()) + .flat_map(|(counter, body)| cnst(false, &format!("({name})::{counter}"), body)); + chain!(main_const, kw_consts, body_consts).collect() +} + +macro_rules! mactree { + ($i:expr; $($body:tt)*) => { + $crate::macros::utils::mactreev!($i; ($($body)*)).remove(0) + }; +} + +macro_rules! mactreev { + (@RECUR $i:expr; $ret:ident) => {}; + (@RECUR $i:expr; $ret:ident "..$" $name:ident $prio:literal $($tail:tt)*) => { + ret.push(MacTok::Ph(Ph{ + name: i.i(stringify!($name)).await, + kind: PhKind::Vector{ at_least_one: false, priority: $prio } + }).at(Pos::Inherit)); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident "...$" $name:ident $prio:literal $($tail:tt)*) => { + $ret.push(MacTok::Ph(Ph{ + name: $i.i(stringify!($name)).await, + kind: $crate::macros::mactree::PhKind::Vector{ at_least_one: true, priority: $prio } + }).at(orchid_base::location::Pos::Inherit)); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident "$" $name:ident $($tail:tt)*) => { + $ret.push(MacTok::Ph(Ph{ + name: $i.i(stringify!(name)).await, + kind: $crate::macros::mactree::PhKind::Scalar + }).at(orchid_base::location::Pos::Inherit)); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident "'" $arg:expr ; $($tail:tt)*) => { + $ret.push(MacTok::Value($arg).at(orchid_base::location::Pos::Inherit)); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident "" $arg:expr ; $($tail:tt)*) => { + $ret.push($arg); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident "l_" $arg:expr ; ($($body:tt)*) $($tail:tt)*) => { + $ret.push(MacTok::Lambda( + MacTok::Value($arg).at(orchid_base::location::Pos::Inherit), + mactreev!(i; $($body)*) + ).at(Pos::Inherit)); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident "l" $argh:tt $(:: $arg:tt)* ($($body:tt)*) $($tail:tt)*) => { + $ret.push(MacTok::Lambda( + MacTok::Name(sym!($argh $(:: $arg)*; $i).await).at(orchid_base::location::Pos::Inherit), + mactreev!(i; $($body)*) + ).at(orchid_base::location::Pos::Inherit)); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident ( $($body:tt)* ) $($tail:tt)*) => { + $ret.push( + MacTok::S(orchid_base::tree::Paren::Round, mactreev!($i; $($body)*)) + .at(orchid_base::location::Pos::Inherit) + ); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident [ $($body:tt)* ] $($tail:tt)*) => { + $ret.push( + MacTok::S(orchid_base::tree::Paren::Square, mactreev!($i; $($body)*)) + .at(orchid_base::location::Pos::Inherit) + ); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident { $($body:tt)* } $($tail:tt)*) => { + $ret.push( + MacTok::S(orchid_base::tree::Paren::Curly, mactreev!($i; $($body)*)) + .at(orchid_base::location::Pos::Inherit) + ); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + (@RECUR $i:expr; $ret:ident $nhead:tt $($tail:tt)*) => { + mactreev!(@NAME_MUNCHER $i; $ret ($nhead) $($tail)*) + }; + (@NAME_MUNCHER $i:expr; $ret:ident ($($munched:tt)*) :: $name:tt $($tail:tt)*) => { + mactreev!(@NAME_MUNCHER $i; $ret ($($munched)* :: $name) $($tail)*) + }; + (@NAME_MUNCHER $i:expr; $ret:ident ($($munched:tt)*) $($tail:tt)*) => { + let sym = orchid_base::sym!($($munched)* ; $i).await; + $ret.push(MacTok::Name(sym).at(orchid_base::location::Pos::Inherit)); + mactreev!(@RECUR $i; $ret $($tail)*); + }; + ($i:expr; ) => { Vec::new() }; + ($i:expr; $($tail:tt)*) => { + { + let mut ret = Vec::::new(); + mactreev!(@RECUR $i; ret $($tail)*); + ret + } + }; +} +pub(crate) use {mactree, mactreev}; diff --git a/orchid-std/src/std/number/num_atom.rs b/orchid-std/src/std/number/num_atom.rs index 8b1e238..919b885 100644 --- a/orchid-std/src/std/number/num_atom.rs +++ b/orchid-std/src/std/number/num_atom.rs @@ -2,7 +2,7 @@ use orchid_api_derive::Coding; use orchid_base::error::OrcRes; use orchid_base::format::FmtUnit; use orchid_base::number::Numeric; -use orchid_extension::atom::{AtomFactory, Atomic, AtomicFeatures, ToAtom, TypAtom}; +use orchid_extension::atom::{AtomFactory, Atomic, AtomicFeatures, ToAtom, TAtom}; use orchid_extension::atom_thin::{ThinAtom, ThinVariant}; use orchid_extension::conv::TryFromExpr; use orchid_extension::expr::Expr; @@ -21,7 +21,7 @@ impl ThinAtom for Int { } impl TryFromExpr for Int { async fn try_from_expr(expr: Expr) -> OrcRes { - TypAtom::::try_from_expr(expr).await.map(|t| t.value) + TAtom::::try_from_expr(expr).await.map(|t| t.value) } } @@ -47,7 +47,7 @@ impl TryFromExpr for Num { Ok(t) => return Ok(Num(Numeric::Int(t.0))), Err(e) => e, }; - match TypAtom::::try_from_expr(expr).await { + match TAtom::::try_from_expr(expr).await { Ok(t) => Ok(Num(Numeric::Float(t.0))), Err(e2) => Err(e + e2), } diff --git a/orchid-std/src/std/string/str_atom.rs b/orchid-std/src/std/string/str_atom.rs index 8f828e1..b621173 100644 --- a/orchid-std/src/std/string/str_atom.rs +++ b/orchid-std/src/std/string/str_atom.rs @@ -9,7 +9,7 @@ use orchid_api_traits::{Encode, Request}; use orchid_base::error::{OrcRes, mk_errv}; use orchid_base::format::{FmtCtx, FmtUnit}; use orchid_base::interner::Tok; -use orchid_extension::atom::{AtomMethod, Atomic, MethodSetBuilder, Supports, TypAtom}; +use orchid_extension::atom::{AtomMethod, Atomic, MethodSetBuilder, Supports, TAtom}; use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant}; use orchid_extension::conv::TryFromExpr; use orchid_extension::expr::Expr; @@ -89,8 +89,8 @@ pub struct OrcString { #[derive(Clone)] pub enum OrcStringKind { - Val(TypAtom), - Int(TypAtom), + Val(TAtom), + Int(TAtom), } impl OrcString { pub async fn get_string(&self) -> Rc { @@ -103,11 +103,11 @@ impl OrcString { impl TryFromExpr for OrcString { async fn try_from_expr(expr: Expr) -> OrcRes { - if let Ok(v) = TypAtom::::try_from_expr(expr.clone()).await { + if let Ok(v) = TAtom::::try_from_expr(expr.clone()).await { return Ok(OrcString { ctx: expr.ctx(), kind: OrcStringKind::Val(v) }); } let ctx = expr.ctx(); - match TypAtom::::try_from_expr(expr).await { + match TAtom::::try_from_expr(expr).await { Ok(t) => Ok(OrcString { ctx: t.untyped.ctx().clone(), kind: OrcStringKind::Int(t) }), Err(e) => Err(mk_errv(ctx.i().i("A string was expected").await, "", e.pos_iter())), } diff --git a/orcx/src/main.rs b/orcx/src/main.rs index bb3ac80..d97a735 100644 --- a/orcx/src/main.rs +++ b/orcx/src/main.rs @@ -10,10 +10,10 @@ use std::rc::Rc; use async_fn_stream::try_stream; use camino::Utf8PathBuf; use clap::{Parser, Subcommand}; -use futures::{Stream, TryStreamExt, io}; +use futures::{FutureExt, Stream, TryStreamExt, io}; use itertools::Itertools; use orchid_base::error::Reporter; -use orchid_base::format::{FmtCtxImpl, Format, take_first}; +use orchid_base::format::{FmtCtxImpl, Format, fmt, take_first}; use orchid_base::location::SrcRange; use orchid_base::logging::{LogStrategy, Logger}; use orchid_base::name::{NameLike, VPath}; @@ -29,6 +29,7 @@ use orchid_host::parse::{HostParseCtxImpl, parse_item, parse_items}; use orchid_host::parsed::{Item, ItemKind, ParsTokTree, ParsedMember, 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}; @@ -61,6 +62,12 @@ pub enum Commands { file: Utf8PathBuf, }, Repl, + ModTree { + #[arg(long)] + proj: Option, + #[arg(long)] + prefix: Option, + }, Exec { #[arg(long)] proj: Option, @@ -226,6 +233,53 @@ async fn main() -> io::Result { } } }, + Commands::ModTree { proj, prefix } => { + let reporter = Reporter::new(); + 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 parse_folder(&root, path, sym!(src; i).await, &reporter, 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, i).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(&root.ctx.i).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, &root.ctx.i).await) + }, + } + } + } + }, Commands::Exec { proj, code } => { let reporter = Reporter::new(); let path = sym!(usercode; i).await; diff --git a/orcx/src/parse_folder.rs b/orcx/src/parse_folder.rs index 799c955..e8f50ac 100644 --- a/orcx/src/parse_folder.rs +++ b/orcx/src/parse_folder.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use futures::FutureExt; use itertools::Itertools; -use orchid_base::error::{OrcRes, Reporter, async_io_err, mk_errv, os_str_to_string}; +use orchid_base::error::{OrcRes, Reporter, async_io_err, os_str_to_string}; use orchid_base::location::SrcRange; use orchid_base::name::Sym; use orchid_base::parse::Snippet; @@ -29,16 +29,6 @@ pub async fn parse_folder( async fn recur(path: &Path, ns: Sym, rep: &Reporter, ctx: Ctx) -> OrcRes> { let sr = SrcRange::new(0..0, &ns); if path.is_dir() { - let Some(name_os) = path.file_name() else { - return Err(mk_errv( - ctx.i.i("Could not read directory name").await, - format!("Path {} ends in ..", path.to_string_lossy()), - [sr], - )); - }; - let name = ctx.i.i(os_str_to_string(name_os, &ctx.i, [sr]).await?).await; - let ns = ns.suffix([name.clone()], &ctx.i).await; - let sr = SrcRange::new(0..0, &ns); let mut items = Vec::new(); let mut stream = match fs::read_dir(path).await { Err(err) => return Err(async_io_err(err, &ctx.i, [sr]).await), @@ -53,6 +43,10 @@ pub async fn parse_folder( continue; }, }; + let os_name = entry.path().file_stem().expect("File name could not be read").to_owned(); + let name = ctx.i.i(os_str_to_string(&os_name, &ctx.i, [sr.clone()]).await?).await; + let ns = ns.suffix([name.clone()], &ctx.i).await; + let sr = SrcRange::new(0..0, &ns); match recur(&entry.path(), ns.clone(), rep, ctx.clone()).boxed_local().await { Err(e) => { rep.report(e); @@ -64,10 +58,6 @@ pub async fn parse_folder( } Ok(Some(ParsedModule::new(false, items))) } else if path.extension() == Some(OsStr::new("orc")) { - let name_os = path.file_stem().expect("If there is an extension, there must be a stem"); - let name = ctx.i.i(os_str_to_string(name_os, &ctx.i, [sr]).await?).await; - let ns = ns.suffix([name], &ctx.i).await; - let sr = SrcRange::new(0..0, &ns); let mut file = match File::open(path).await { Err(e) => return Err(async_io_err(e, &ctx.i, [sr]).await), Ok(file) => file,