task_local context over context objects

- interner impls logically separate from API in orchid-base (default host interner still in base for testing)
- error reporting, logging, and a variety of other features passed down via context in extension, not yet in host to maintain library-ish profile, should consider options
- no global spawn mechanic, the host has a spawn function but extensions only get a stash for enqueuing async work in sync callbacks which is then explicitly, manually, and with strict order popped and awaited
- still deadlocks nondeterministically for some ungodly reason
This commit is contained in:
2026-01-01 14:54:29 +00:00
parent 06debb3636
commit 32d6237dc5
92 changed files with 2507 additions and 2223 deletions

View File

@@ -4,15 +4,14 @@ use async_fn_stream::stream;
use async_once_cell::OnceCell;
use futures::StreamExt;
use itertools::Itertools;
use orchid_base::error::{OrcRes, Reporter, mk_errv};
use orchid_base::error::{OrcRes, mk_errv, report, with_reporter};
use orchid_base::interner::is;
use orchid_base::parse::{
Comment, ParseCtx, Parsed, Snippet, expect_end, expect_tok, line_items, token_errv,
try_pop_no_fluff,
Comment, 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::TAtom;
use orchid_extension::context::i;
use orchid_extension::conv::{ToExpr, TryFromExpr};
use orchid_extension::gen_expr::{call, sym_ref};
use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser};
@@ -35,22 +34,22 @@ impl Parser for MacroLine {
) -> OrcRes<Vec<ParsedLine>> {
if exported {
return Err(mk_errv(
ctx.i().i("macros are always exported").await,
is("macros are always exported").await,
"The export keyword is forbidden here to avoid confusion\n\
because macros are exported by default",
[line.sr()],
));
}
let module = ctx.module();
let Parsed { output: prio_or_body, tail } = try_pop_no_fluff(&ctx, line).await?;
let Parsed { output: prio_or_body, tail } = try_pop_no_fluff(line).await?;
let bad_first_item_err = || {
token_errv(&ctx, prio_or_body, "Expected priority or block", |s| {
token_errv(prio_or_body, "Expected priority or block", |s| {
format!("Expected a priority number or a () block, found {s}")
})
};
let (prio, body) = match &prio_or_body.tok {
Token::S(Paren::Round, body) => {
expect_end(&ctx, tail).await?;
expect_end(tail).await?;
(None, body)
},
Token::Handle(expr) => match TAtom::<Int>::try_from_expr(expr.clone()).await {
@@ -58,33 +57,32 @@ impl Parser for MacroLine {
return Err(e + bad_first_item_err().await);
},
Ok(prio) => {
let Parsed { output: body, tail } = try_pop_no_fluff(&ctx, tail).await?;
let Parsed { output: body, tail } = try_pop_no_fluff(tail).await?;
let Token::S(Paren::Round, block) = &body.tok else {
return Err(
token_errv(&ctx, prio_or_body, "Expected () block", |s| {
token_errv(prio_or_body, "Expected () block", |s| {
format!("Expected a () block, found {s}")
})
.await,
);
};
expect_end(&ctx, tail).await?;
expect_end(tail).await?;
(Some(prio), block)
},
},
_ => return Err(bad_first_item_err().await),
};
let lines = line_items(&ctx, Snippet::new(prio_or_body, body)).await;
let lines = line_items(Snippet::new(prio_or_body, body)).await;
let Some((kw_line, rule_lines)) = lines.split_first() else { return Ok(Vec::new()) };
let mut keywords = Vec::new();
let Parsed { tail: kw_tail, .. } =
expect_tok(&ctx, kw_line.tail, ctx.i().i("keywords").await).await?;
let Parsed { tail: kw_tail, .. } = expect_tok(kw_line.tail, is("keywords").await).await?;
for kw_tok in kw_tail.iter().filter(|kw| !kw.is_fluff()) {
match kw_tok.as_name() {
Some(kw) => {
keywords.push((kw, kw_tok.sr()));
},
None => ctx.rep().report(
token_errv(&ctx, kw_tok, "invalid macro keywords list", |tok| {
None => report(
token_errv(kw_tok, "invalid macro keywords list", |tok| {
format!("The keywords list must be a sequence of names; received {tok}")
})
.await,
@@ -93,7 +91,7 @@ impl Parser for MacroLine {
}
let Some((macro_name, _)) = keywords.first().cloned() else {
return Err(mk_errv(
ctx.i().i("macro with no keywords").await,
is("macro with no keywords").await,
"Macros must define at least one macro of their own.",
[kw_line.tail.sr()],
));
@@ -102,18 +100,18 @@ impl Parser for MacroLine {
let mut lines = Vec::new();
for (idx, line) in rule_lines.iter().enumerate().map(|(n, v)| (n as u32, v)) {
let sr = line.tail.sr();
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 name = is(&format!("rule::{}::{}", macro_name, idx)).await;
let Parsed { tail, .. } = expect_tok(line.tail, is("rule").await).await?;
let arrow_token = is("=>").await;
let Some((pattern, body)) = tail.split_once(|tok| tok.is_kw(arrow_token.clone())) else {
ctx.rep().report(mk_errv(
ctx.i().i("Missing => in rule").await,
report(mk_errv(
is("Missing => in rule").await,
"Rule lines are of the form `rule ...pattern => ...body`",
[line.tail.sr()],
));
continue;
};
let pattern = parse_tokv(pattern, &ctx).await;
let pattern = parse_tokv(pattern).await;
let mut placeholders = Vec::new();
pattern.map(&mut false, &mut |tok| {
if let MacTok::Ph(ph) = tok.tok() {
@@ -121,9 +119,9 @@ impl Parser for MacroLine {
}
None
});
let mut body_mactree = parse_tokv(body, &ctx).await;
let mut body_mactree = parse_tokv(body).await;
for (ph, ph_pos) in placeholders.iter().rev() {
let name = ctx.module().suffix([ph.name.clone()], ctx.i()).await;
let name = ctx.module().suffix([ph.name.clone()]).await;
body_mactree =
MacTreeSeq::new([
MacTok::Lambda(MacTok::Name(name).at(ph_pos.clone()), body_mactree).at(ph_pos.clone())
@@ -132,45 +130,40 @@ impl Parser for MacroLine {
let body_sr = body.sr();
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;
let macro_input = MacTok::S(Paren::Round, body).at(body_sr.pos());
if let Some(e) = rep.errv() {
return Err(e);
}
Ok(call(sym_ref(sym!(macros::resolve; i())), [macro_input.to_gen().await]))
let macro_input =
MacTok::S(Paren::Round, with_reporter(dealias_mac_v(&body_mactree, &ctx)).await?)
.at(body_sr.pos());
Ok(call(sym_ref(sym!(macros::resolve)), [macro_input.to_gen().await]))
}))
}
let mac_cell = Rc::new(OnceCell::new());
let rules = Rc::new(rules);
for (kw, sr) in &*keywords {
clone!(mac_cell, rules, module, macro_name, prio);
let kw_key = i().i(&format!("__macro__{kw}")).await;
let kw_key = is(&format!("__macro__{kw}")).await;
lines.push(ParsedLine::cnst(&sr.clone(), &comments, true, kw_key, async move |cctx| {
let mac_future = async {
let rep = Reporter::new();
let rules = stream(async |mut h| {
for (body, ph_names, pattern_rel) in rules.iter() {
let pattern = dealias_mac_v(pattern_rel, &cctx, &rep).await;
let ph_names = ph_names.iter().map(|(ph, _)| ph.name.clone()).collect_vec();
match Matcher::new(pattern.clone()).await {
Ok(matcher) =>
h.emit(Rule { body: body.clone(), matcher, pattern, ph_names }).await,
Err(e) => rep.report(e),
let rules = with_reporter(
stream(async |mut h| {
for (body, ph_names, pattern_rel) in rules.iter() {
let pattern = dealias_mac_v(pattern_rel, &cctx).await;
let ph_names = ph_names.iter().map(|(ph, _)| ph.name.clone()).collect_vec();
match Matcher::new(pattern.clone()).await {
Ok(matcher) =>
h.emit(Rule { body: body.clone(), matcher, pattern, ph_names }).await,
Err(e) => report(e),
}
}
}
})
.collect::<Vec<_>>()
.await;
match rep.errv() {
Some(e) => Err(e),
None => Ok(Macro(Rc::new(MacroData {
canonical_name: module.suffix([macro_name], &i()).await,
module,
prio: prio.map(|i| i.0 as u64),
rules,
}))),
}
})
.collect::<Vec<_>>(),
)
.await?;
Ok(Macro(Rc::new(MacroData {
canonical_name: module.suffix([macro_name]).await,
module,
prio: prio.map(|i| i.0 as u64),
rules,
})))
};
mac_cell.get_or_init(mac_future).await.clone().to_gen().await
}))