use futures::future::join_all; use itertools::Itertools; use orchid_base::error::{OrcRes, Reporter, mk_errv}; use orchid_base::format::fmt; use orchid_base::interner::{Interner, Tok}; use orchid_base::name::Sym; use orchid_base::parse::{ Comment, Import, ParseCtx, Parsed, Snippet, expect_end, line_items, parse_multiname, try_pop_no_fluff, }; use orchid_base::tree::{Paren, TokTree, Token}; use substack::Substack; use crate::ctx::Ctx; use crate::expr::Expr; use crate::parsed::{Item, ItemKind, ParsedMember, ParsedMemberKind, ParsedModule}; use crate::system::System; type ParsSnippet<'a> = Snippet<'a, Expr, Expr>; pub struct HostParseCtxImpl<'a> { pub ctx: Ctx, pub src: Sym, pub systems: &'a [System], pub rep: &'a Reporter, } impl ParseCtx for HostParseCtxImpl<'_> { fn rep(&self) -> &Reporter { self.rep } fn i(&self) -> &Interner { &self.ctx.i } } impl HostParseCtx for HostParseCtxImpl<'_> { fn ctx(&self) -> &Ctx { &self.ctx } fn systems(&self) -> impl Iterator { self.systems.iter() } fn src_path(&self) -> Sym { self.src.clone() } } pub trait HostParseCtx: ParseCtx { #[must_use] fn ctx(&self) -> &Ctx; #[must_use] fn systems(&self) -> impl Iterator; #[must_use] fn src_path(&self) -> Sym; } pub async fn parse_items( ctx: &impl HostParseCtx, path: Substack<'_, Tok>, 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()) } pub async fn parse_item( ctx: &impl HostParseCtx, path: Substack<'_, Tok>, comments: Vec, item: ParsSnippet<'_>, ) -> OrcRes> { match item.pop_front() { Some((TokTree { tok: Token::Name(n), .. }, postdisc)) => match n { n if *n == ctx.i().i("export").await => match try_pop_no_fluff(ctx, postdisc).await? { Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } => parse_exportable_item(ctx, path, comments, true, n.clone(), tail).await, Parsed { output, tail: _ } => Err(mk_errv( ctx.i().i("Malformed export").await, "`export` can either prefix other lines or list names inside ( )", [output.sr()], )), }, n if *n == ctx.i().i("import").await => { let imports = parse_import(ctx, postdisc).await?; Ok(Vec::from_iter(imports.into_iter().map(|t| Item { comments: comments.clone(), sr: t.sr.clone(), kind: ItemKind::Import(t), }))) }, n => parse_exportable_item(ctx, path, comments, false, n.clone(), postdisc).await, }, Some(_) => Err(mk_errv( ctx.i().i("Expected a line type").await, "All lines must begin with a keyword", [item.sr()], )), None => unreachable!("These lines are filtered and aggregated in earlier stages"), } } pub async fn parse_import<'a>( ctx: &impl HostParseCtx, tail: ParsSnippet<'a>, ) -> OrcRes> { let Parsed { output: imports, tail } = parse_multiname(ctx, tail).await?; expect_end(ctx, tail).await?; Ok(imports) } pub async fn parse_exportable_item<'a>( ctx: &impl HostParseCtx, path: Substack<'_, Tok>, comments: Vec, exported: bool, discr: Tok, tail: ParsSnippet<'a>, ) -> OrcRes> { let kind = if discr == ctx.i().i("mod").await { let (name, body) = parse_module(ctx, path, tail).await?; ItemKind::Member(ParsedMember { name, exported, kind: ParsedMemberKind::Mod(body) }) } else if let Some(parser) = ctx.systems().find_map(|s| s.get_parser(discr.clone())) { return parser .parse(ctx, path, tail.to_vec(), exported, comments, &mut async |stack, lines| { let source = Snippet::new(lines.first().unwrap(), &lines); parse_items(ctx, stack, source).await }) .await; } else { let ext_lines = ctx.systems().flat_map(System::line_types).join(", "); return Err(mk_errv( ctx.i().i("Unrecognized line type").await, format!("Line types are: mod, {ext_lines}"), [tail.prev().sr()], )); }; Ok(vec![Item { comments, sr: tail.sr(), kind }]) } pub async fn parse_module<'a>( ctx: &impl HostParseCtx, path: Substack<'_, Tok>, tail: ParsSnippet<'a>, ) -> OrcRes<(Tok, ParsedModule)> { let (name, tail) = match try_pop_no_fluff(ctx, tail).await? { Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } => (n.clone(), tail), Parsed { output, .. } => { return Err(mk_errv( ctx.i().i("Missing module name").await, format!("A name was expected, {} was found", fmt(output, ctx.i()).await), [output.sr()], )); }, }; let Parsed { output, tail: surplus } = try_pop_no_fluff(ctx, tail).await?; expect_end(ctx, surplus).await?; let Some(body) = output.as_s(Paren::Round) else { return Err(mk_errv( ctx.i().i("Expected module body").await, format!("A ( block ) was expected, {} was found", fmt(output, ctx.i()).await), [output.sr()], )); }; let path = path.push(name.clone()); Ok((name, ParsedModule::new(true, parse_items(ctx, path, body).await?))) }