use std::marker::PhantomData; use async_stream::stream; use futures::future::{LocalBoxFuture, join_all}; use futures::{FutureExt, Stream, StreamExt, pin_mut}; use itertools::Itertools; use never::Never; use orchid_api::ResolveNames; use orchid_base::error::{OrcRes, Reporter}; use orchid_base::id_store::IdStore; use orchid_base::interner::{Interner, Tok}; use orchid_base::location::SrcRange; use orchid_base::name::Sym; use orchid_base::parse::{Comment, ParseCtx, Snippet}; use orchid_base::reqnot::{ReqHandlish, Requester}; use orchid_base::tree::{TokTree, Token, ttv_into_api}; use crate::api; use crate::expr::Expr; use crate::gen_expr::GExpr; use crate::system::{SysCtx, SysCtxEntry}; use crate::tree::GenTokTree; pub type PTok = Token; pub type PTokTree = TokTree; pub type PSnippet<'a> = Snippet<'a, Expr, Never>; pub trait Parser: Send + Sync + Sized + Default + 'static { const LINE_HEAD: &'static str; fn parse<'a>( ctx: ParsCtx<'a>, exported: bool, comments: Vec, line: PSnippet<'a>, ) -> impl Future>> + 'a; } pub trait DynParser: Send + Sync + 'static { fn line_head(&self) -> &'static str; fn parse<'a>( &self, ctx: ParsCtx<'a>, exported: bool, comments: Vec, line: PSnippet<'a>, ) -> LocalBoxFuture<'a, OrcRes>>; } impl DynParser for T { fn line_head(&self) -> &'static str { Self::LINE_HEAD } fn parse<'a>( &self, ctx: ParsCtx<'a>, exported: bool, comments: Vec, line: PSnippet<'a>, ) -> LocalBoxFuture<'a, OrcRes>> { Box::pin(async move { Self::parse(ctx, exported, comments, line).await }) } } pub type ParserObj = &'static dyn DynParser; pub struct ParsCtx<'a> { _parse: PhantomData<&'a mut ()>, ctx: SysCtx, module: Sym, reporter: &'a Reporter, } impl<'a> ParsCtx<'a> { pub(crate) fn new(ctx: SysCtx, module: Sym, reporter: &'a Reporter) -> Self { Self { _parse: PhantomData, ctx, module, reporter } } pub fn ctx(&self) -> &SysCtx { &self.ctx } pub fn module(&self) -> Sym { self.module.clone() } } impl ParseCtx for ParsCtx<'_> { fn i(&self) -> &Interner { self.ctx.i() } fn rep(&self) -> &Reporter { self.reporter } } type BoxConstCallback = Box LocalBoxFuture<'static, GExpr>>; #[derive(Default)] struct ParsedConstCtxEntry { consts: IdStore, } impl SysCtxEntry for ParsedConstCtxEntry {} pub struct ParsedLine { pub sr: SrcRange, pub comments: Vec, pub kind: ParsedLineKind, } impl ParsedLine { pub async fn into_api(self, ctx: SysCtx, hand: &dyn ReqHandlish) -> api::ParsedLine { api::ParsedLine { comments: self.comments.into_iter().map(|c| c.to_api()).collect(), source_range: self.sr.to_api(), kind: match self.kind { ParsedLineKind::Mem(mem) => api::ParsedLineKind::Member(api::ParsedMember { name: mem.name.to_api(), exported: mem.exported, kind: match mem.kind { ParsedMemKind::Const(cb) => api::ParsedMemberKind::Constant(api::ParsedConstId( ctx.get_or_default::().consts.add(cb).id(), )), ParsedMemKind::Mod { lines, use_prelude } => api::ParsedMemberKind::Module { lines: linev_into_api(lines, ctx, hand).boxed_local().await, use_prelude, }, }, }), ParsedLineKind::Rec(tv) => api::ParsedLineKind::Recursive(ttv_into_api(tv, &mut (), &mut (ctx, hand)).await), }, } } } pub(crate) async fn linev_into_api( v: Vec, ctx: SysCtx, hand: &dyn ReqHandlish, ) -> Vec { join_all(v.into_iter().map(|l| l.into_api(ctx.clone(), hand))).await } pub enum ParsedLineKind { Mem(ParsedMem), Rec(Vec), } pub struct ParsedMem { pub name: Tok, pub exported: bool, pub kind: ParsedMemKind, } pub enum ParsedMemKind { Const(BoxConstCallback), Mod { lines: Vec, use_prelude: bool }, } impl ParsedMemKind { pub fn cnst GExpr + 'static>(f: F) -> Self { Self::Const(Box::new(|ctx| Box::pin(f(ctx)))) } pub fn module(lines: impl IntoIterator) -> Self { Self::Mod { lines: lines.into_iter().collect(), use_prelude: true } } pub fn clean_module(lines: impl IntoIterator) -> Self { Self::Mod { lines: lines.into_iter().collect(), use_prelude: false } } } /* TODO: how the macro runner uses the multi-stage loader Since the macro runner actually has to invoke the interpreter, it'll run at const-time and not at postparse-time anyway. pasing stage establishes the role of every constant as a macro keyword postparse / const load links up constants with every macro they can directly invoke the constants representing the keywords might not actually be postparsed, \ the connection is instead made by detecting in the macro system that the \ resolved name is owned by a macro the returned constant from this call is always an entrypoint call to \ the macro system the constants representing the keywords resolve to panic execute relies on these links detected in the extension to dispatch relevant macros */ pub struct ConstCtx { ctx: SysCtx, constid: api::ParsedConstId, } impl ConstCtx { pub fn names<'a>( &'a self, names: impl IntoIterator + 'a, ) -> impl Stream> + 'a { let resolve_names = ResolveNames { constid: self.constid, sys: self.ctx.sys_id(), names: names.into_iter().map(|n| n.to_api()).collect_vec(), }; stream! { for name_opt in self.ctx.reqnot().request(resolve_names).await { yield match name_opt { None => None, Some(name) => Some(Sym::from_api(name, self.ctx.i()).await) } } } } pub async fn names_n(&self, names: [&Sym; N]) -> [Option; N] { let mut results = [const { None }; N]; let names = self.names(names).enumerate().filter_map(|(i, n)| async move { Some((i, n?)) }); pin_mut!(names); while let Some((i, name)) = names.next().await { results[i] = Some(name); } results } } pub(crate) async fn get_const(id: api::ParsedConstId, ctx: SysCtx) -> GExpr { let ent = ctx.get::(); let rec = ent.consts.get(id.0).expect("Bad ID or double read of parsed const"); let ctx = ConstCtx { constid: id, ctx: ctx.clone() }; rec.remove()(ctx).await }