160 lines
5.0 KiB
Rust
160 lines
5.0 KiB
Rust
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<Item = &System> { 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<Item = &System>;
|
|
#[must_use]
|
|
fn src_path(&self) -> Sym;
|
|
}
|
|
|
|
pub async fn parse_items(
|
|
ctx: &impl HostParseCtx,
|
|
path: Substack<'_, Tok<String>>,
|
|
items: ParsSnippet<'_>,
|
|
) -> OrcRes<Vec<Item>> {
|
|
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<String>>,
|
|
comments: Vec<Comment>,
|
|
item: ParsSnippet<'_>,
|
|
) -> OrcRes<Vec<Item>> {
|
|
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<Vec<Import>> {
|
|
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<String>>,
|
|
comments: Vec<Comment>,
|
|
exported: bool,
|
|
discr: Tok<String>,
|
|
tail: ParsSnippet<'a>,
|
|
) -> OrcRes<Vec<Item>> {
|
|
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<String>>,
|
|
tail: ParsSnippet<'a>,
|
|
) -> OrcRes<(Tok<String>, 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?)))
|
|
}
|