First steps for the macro system

This commit is contained in:
2025-08-01 18:32:55 +02:00
parent f87185ef88
commit 051b5e666f
18 changed files with 356 additions and 166 deletions

View File

@@ -1,9 +1,7 @@
use std::borrow::Cow;
use std::pin::Pin;
use std::rc::Rc;
use futures::AsyncWrite;
use itertools::Itertools;
use never::Never;
use orchid_extension::atom::{Atomic, TypAtom};
use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant, get_own_instance};

View File

@@ -1,20 +1,17 @@
use futures::future::LocalBoxFuture;
use async_std::stream;
use async_stream::stream;
use futures::{FutureExt, StreamExt};
use hashbrown::HashMap;
use itertools::Itertools;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::interner::Tok;
use orchid_base::name::Sym;
use orchid_base::parse::{Comment, ParseCtx, Parsed, expect_tok, token_errv, try_pop_no_fluff};
use orchid_extension::gen_expr::GExpr;
use orchid_extension::parser::{
ConstCtx, GenSnippet, ParsCtx, ParsedLine, ParsedLineKind, ParsedMem, ParsedMemKind, Parser,
use orchid_base::error::OrcRes;
use orchid_base::parse::{
Comment, ParseCtx, Parsed, Snippet, expect_tok, token_errv, try_pop_no_fluff,
};
use orchid_extension::parser::{
PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, ParsedLineKind, ParsedMem, ParsedMemKind, Parser,
};
use substack::Substack;
use crate::macros::mactree::{MacTok, MacTree, map_mactree, map_mactree_v};
type ExprGenerator =
Box<dyn for<'a> FnOnce(ConstCtx, Substack<'a, Sym>) -> LocalBoxFuture<'a, GExpr>>;
use crate::macros::mactree::{MacTok, MacTree, map_mactree_v};
#[derive(Default)]
pub struct LetLine;
@@ -24,7 +21,7 @@ impl Parser for LetLine {
ctx: ParsCtx<'a>,
exported: bool,
comments: Vec<Comment>,
line: GenSnippet<'a>,
line: PSnippet<'a>,
) -> OrcRes<Vec<ParsedLine>> {
let Parsed { output: name_tok, tail } = try_pop_no_fluff(&ctx, line).await?;
let Some(name) = name_tok.as_name() else {
@@ -34,19 +31,11 @@ impl Parser for LetLine {
return Err(err.await);
};
let Parsed { tail, .. } = expect_tok(&ctx, tail, ctx.i().i("=").await).await?;
fn parse_tokv(line: GenSnippet<'_>) -> Vec<MacTree> {
let first: MacTree = if let Some((idx, arg)) =
line.iter().enumerate().find_map(|(i, x)| Some((i, x.as_lambda()?)))
{
} else {
};
todo!()
}
let mut names = HashMap::new();
let aliased = parse_tokv(tail);
let aliased = parse_tokv(tail, &ctx).await;
map_mactree_v(&aliased, &mut false, &mut |tpl| {
if let MacTok::Name(n) = &*tpl.tok {
names.insert(n.clone(), n);
names.insert(n.clone(), n.clone());
}
None
});
@@ -56,14 +45,76 @@ impl Parser for LetLine {
kind: ParsedLineKind::Mem(ParsedMem {
exported,
name,
kind: ParsedMemKind::cnst(async |ctx| {
let mut names_str = ctx.names(names.keys());
while let Some(()) = names_str.next().await {
names[]
kind: ParsedMemKind::cnst(async move |ctx| {
let keys = names.keys().cloned().collect_vec();
let names_mut = &mut names;
stream! {
for await (canonical, local) in ctx.names(&keys).zip(stream::from_iter(&keys)) {
if let Some(name) = canonical {
*names_mut.get_mut(local).expect("Queried specifically the keys of this map") = name
}
}
}
todo!()
.collect::<()>()
.await;
let macro_input = map_mactree_v(&aliased, &mut false, &mut |tree| match &*tree.tok {
MacTok::Name(n) => names.get(n).map(|new_n| MacTok::Name(new_n.clone()).at(tree.pos())),
_ => None,
});
todo!("Run macros then convert this into an expr")
}),
}),
}])
}
}
async fn parse_tokv(line: PSnippet<'_>, ctx: &ParsCtx<'_>) -> Vec<MacTree> {
if let Some((idx, arg)) = line.iter().enumerate().find_map(|(i, x)| Some((i, x.as_lambda()?))) {
let (head, lambda) = line.split_at(idx as u32);
let (_, body) = lambda.pop_front().unwrap();
let body = parse_tokv(body, ctx).boxed_local().await;
let mut all = parse_tokv_no_lambdas(&head, ctx).await;
match parse_tok(arg, ctx).await {
Some(arg) => all.push(MacTok::Lambda(arg, body).at(lambda.sr().pos())),
None => ctx.rep().report(
token_errv(ctx, arg, "Lambda argument fluff", |arg| {
format!("Lambda arguments must be a valid token, found meaningless fragment {arg}")
})
.await,
),
};
all
} else {
parse_tokv_no_lambdas(&line, ctx).await
}
}
async fn parse_tokv_no_lambdas(line: &[PTokTree], ctx: &ParsCtx<'_>) -> Vec<MacTree> {
stream::from_iter(line).filter_map(|tt| parse_tok(tt, ctx)).collect().await
}
async fn parse_tok(tree: &PTokTree, ctx: &ParsCtx<'_>) -> Option<MacTree> {
let tok = match &tree.tok {
PTok::Bottom(errv) => MacTok::Bottom(errv.clone()),
PTok::BR | PTok::Comment(_) => return None,
PTok::Name(n) => MacTok::Name(ctx.module().suffix([n.clone()], ctx.i()).await),
PTok::NS(..) => match tree.as_multiname() {
Ok(mn) => MacTok::Name(mn.to_sym(ctx.i()).await),
Err(nested) => {
ctx.rep().report(
token_errv(ctx, tree, ":: can only be followed by a name in an expression", |tok| {
format!("Expected name, found {tok}")
})
.await,
);
return parse_tok(nested, ctx).boxed_local().await;
},
},
PTok::Handle(expr) => MacTok::Value(expr.clone()),
PTok::NewExpr(never) => match *never {},
PTok::LambdaHead(_) => panic!("Lambda-head handled in the sequence parser"),
PTok::S(p, body) =>
MacTok::S(*p, parse_tokv(Snippet::new(tree, body), ctx).boxed_local().await),
};
Some(tok.at(tree.sr().pos()))
}

View File

@@ -2,14 +2,17 @@ use std::borrow::Cow;
use std::fmt::Display;
use std::rc::Rc;
use futures::FutureExt;
use futures::future::join_all;
use itertools::Itertools;
use orchid_api::Paren;
use orchid_base::error::OrcErrv;
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
use orchid_base::interner::Tok;
use orchid_base::location::Pos;
use orchid_base::name::Sym;
use orchid_base::tl_cache;
use orchid_base::tree::indent;
use orchid_extension::atom::Atomic;
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant};
use orchid_extension::expr::Expr;
@@ -20,7 +23,8 @@ pub struct MacTree {
pub tok: Rc<MacTok>,
}
impl MacTree {
fn tok(&self) -> &MacTok { &*self.tok }
pub fn tok(&self) -> &MacTok { &*self.tok }
pub fn pos(&self) -> Pos { self.pos.clone() }
}
impl Atomic for MacTree {
type Data = ();
@@ -46,6 +50,12 @@ pub enum MacTok {
/// Only permitted in "pattern" values produced by macro blocks, which are
/// never accessed as variables by usercode
Ph(Ph),
Bottom(OrcErrv),
}
impl MacTok {
pub fn at(self, pos: impl Into<Pos>) -> MacTree {
MacTree { pos: pos.into(), tok: Rc::new(self) }
}
}
impl Format for MacTok {
async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
@@ -55,7 +65,7 @@ impl Format for MacTok {
tl_cache!(Rc<Variants>: Rc::new(Variants::default()
.unbounded("\\{0b}.{1l}")
.bounded("(\\{0b}.{1b})"))),
[arg.print(c).await, mtreev_fmt(b, c).await],
[arg.print(c).boxed_local().await, mtreev_fmt(b, c).await],
),
Self::Name(n) => format!("{n}").into(),
Self::Ph(ph) => format!("{ph}").into(),
@@ -68,6 +78,8 @@ impl Format for MacTok {
[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(),
}
}
}
@@ -117,7 +129,8 @@ pub fn map_mactree<F: FnMut(MacTree) -> Option<MacTree>>(
ro(changed, |changed| map_mactree(arg, changed, map)),
map_mactree_v(body, changed, map),
),
MacTok::Name(_) | MacTok::Value(_) | MacTok::Slot | MacTok::Ph(_) => return src.clone(),
MacTok::Name(_) | MacTok::Value(_) | MacTok::Slot | MacTok::Ph(_) | MacTok::Bottom(_) =>
return src.clone(),
MacTok::S(p, body) => MacTok::S(*p, map_mactree_v(body, changed, map)),
},
};

View File

@@ -1,5 +1,9 @@
use futures::FutureExt;
use futures::future::join_all;
use itertools::Itertools;
use orchid_base::interner::Tok;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::interner::{Interner, Tok};
use orchid_base::join_ok;
use orchid_base::side::Side;
use super::shared::{AnyMatcher, ScalMatcher, VecMatcher};
@@ -28,24 +32,31 @@ fn scal_cnt<'a>(iter: impl Iterator<Item = &'a MacTree>) -> usize {
}
#[must_use]
pub fn mk_any(pattern: &[MacTree]) -> AnyMatcher {
pub async fn mk_any(pattern: &[MacTree], i: &Interner) -> OrcRes<AnyMatcher> {
let left_split = scal_cnt(pattern.iter());
if pattern.len() <= left_split {
return AnyMatcher::Scalar(mk_scalv(pattern));
return Ok(AnyMatcher::Scalar(mk_scalv(pattern, i).await?));
}
let (left, not_left) = pattern.split_at(left_split);
let right_split = not_left.len() - scal_cnt(pattern.iter().rev());
let (mid, right) = not_left.split_at(right_split);
AnyMatcher::Vec { left: mk_scalv(left), mid: mk_vec(mid), right: mk_scalv(right) }
join_ok! {
left = mk_scalv(left, i).await;
mid = mk_vec(mid, i).await;
right = mk_scalv(right, i).await;
}
Ok(AnyMatcher::Vec { left, mid, right })
}
/// Pattern MUST NOT contain vectorial placeholders
#[must_use]
fn mk_scalv(pattern: &[MacTree]) -> Vec<ScalMatcher> { pattern.iter().map(mk_scalar).collect() }
async fn mk_scalv(pattern: &[MacTree], i: &Interner) -> OrcRes<Vec<ScalMatcher>> {
join_all(pattern.iter().map(|pat| mk_scalar(pat, i))).await.into_iter().collect()
}
/// Pattern MUST start and end with a vectorial placeholder
#[must_use]
pub fn mk_vec(pattern: &[MacTree]) -> VecMatcher {
pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes<VecMatcher> {
debug_assert!(!pattern.is_empty(), "pattern cannot be empty");
debug_assert!(pattern.first().map(vec_attrs).is_some(), "pattern must start with a vectorial");
debug_assert!(pattern.last().map(vec_attrs).is_some(), "pattern must end with a vectorial");
@@ -57,39 +68,57 @@ pub fn mk_vec(pattern: &[MacTree]) -> VecMatcher {
let (l_side, l_sep) = left.split_at(left.len() - l_sep_size);
let main = VecMatcher::Placeh { key: key.clone(), nonzero };
match (left, right) {
(&[], &[]) => VecMatcher::Placeh { key, nonzero },
(&[], _) => VecMatcher::Scan {
direction: Side::Left,
left: Box::new(main),
sep: mk_scalv(r_sep),
right: Box::new(mk_vec(r_side)),
(&[], &[]) => Ok(VecMatcher::Placeh { key, nonzero }),
(&[], _) => {
join_ok! {
sep = mk_scalv(r_sep, i).await;
right = mk_vec(r_side, i).boxed_local().await;
}
Ok(VecMatcher::Scan {
direction: Side::Left,
left: Box::new(main),
sep,
right: Box::new(right),
})
},
(_, &[]) => VecMatcher::Scan {
direction: Side::Right,
left: Box::new(mk_vec(l_side)),
sep: mk_scalv(l_sep),
right: Box::new(main),
(_, &[]) => {
join_ok! {
left = mk_vec(l_side, i).boxed_local().await;
sep = mk_scalv(l_sep, i).await;
}
Ok(VecMatcher::Scan {
direction: Side::Right,
left: Box::new(left),
sep,
right: Box::new(main),
})
},
(..) => {
let mut key_order =
l_side.iter().chain(r_side.iter()).filter_map(vec_attrs).collect::<Vec<_>>();
key_order.sort_by_key(|(_, prio, _)| -(*prio as i64));
VecMatcher::Middle {
left: Box::new(mk_vec(l_side)),
left_sep: mk_scalv(l_sep),
mid: Box::new(main),
right_sep: mk_scalv(r_sep),
right: Box::new(mk_vec(r_side)),
key_order: key_order.into_iter().map(|(n, ..)| n).collect(),
join_ok! {
left = mk_vec(l_side, i).boxed_local().await;
left_sep = mk_scalv(l_sep, i).await;
right_sep = mk_scalv(r_sep, i).await;
right = mk_vec(r_side, i).boxed_local().await;
}
Ok(VecMatcher::Middle {
left: Box::new(left),
left_sep,
mid: Box::new(main),
right_sep,
right: Box::new(right),
key_order: key_order.into_iter().map(|(n, ..)| n).collect(),
})
},
}
}
/// Pattern MUST NOT be a vectorial placeholder
#[must_use]
fn mk_scalar(pattern: &MacTree) -> ScalMatcher {
match &*pattern.tok {
async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes<ScalMatcher> {
Ok(match &*pattern.tok {
MacTok::Name(n) => ScalMatcher::Name(n.clone()),
MacTok::Ph(Ph { name, kind }) => match kind {
PhKind::Vector { .. } => {
@@ -97,10 +126,16 @@ fn mk_scalar(pattern: &MacTree) -> ScalMatcher {
},
PhKind::Scalar => ScalMatcher::Placeh { key: name.clone() },
},
MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body))),
MacTok::Lambda(arg, body) => ScalMatcher::Lambda(Box::new(mk_any(arg)), Box::new(mk_any(body))),
MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body, i).boxed_local().await?)),
MacTok::Lambda(..) =>
return Err(mk_errv(
i.i("Lambda in matcher").await,
"Lambdas can't be matched for, only generated in templates",
[pattern.pos()],
)),
MacTok::Value(_) | MacTok::Slot => panic!("Only used for templating"),
}
MacTok::Bottom(errv) => return Err(errv.clone()),
})
}
#[cfg(test)]
@@ -150,7 +185,7 @@ mod test {
}))
.await,
];
let matcher = mk_any(&pattern);
let matcher = mk_any(&pattern, &i).await.expect("This matcher isn't broken");
println!("{matcher}");
})
}

View File

@@ -2,6 +2,7 @@ use std::fmt;
use std::rc::Rc;
use itertools::Itertools;
use orchid_base::error::OrcRes;
use orchid_base::interner::Interner;
use orchid_base::location::Pos;
use orchid_base::name::Sym;
@@ -20,21 +21,21 @@ pub fn last_is_vec(pattern: &[MacTree]) -> bool { vec_attrs(pattern.last().unwra
pub struct NamedMatcher(AnyMatcher);
impl NamedMatcher {
pub async fn new(pattern: &[MacTree], i: &Interner) -> Self {
pub async fn new(pattern: &[MacTree], i: &Interner) -> OrcRes<Self> {
assert!(
matches!(pattern.first().map(|tree| &*tree.tok), Some(MacTok::Name(_))),
"Named matchers must begin with a name"
);
match last_is_vec(pattern) {
true => Self(mk_any(pattern)),
Ok(Self(match last_is_vec(pattern) {
true => mk_any(pattern, i).await,
false => {
let kind = PhKind::Vector { priority: 0, at_least_one: false };
let tok = MacTok::Ph(Ph { name: i.i("::after").await, kind });
let suffix = [MacTree { pos: Pos::None, tok: Rc::new(tok) }];
Self(mk_any(&pattern.iter().chain(&suffix).cloned().collect_vec()))
mk_any(&pattern.iter().chain(&suffix).cloned().collect_vec(), i).await
},
}
}?))
}
/// Also returns the tail, if any, which should be matched further
/// Note that due to how priod works below, the main usable information from
@@ -62,12 +63,12 @@ impl fmt::Debug for NamedMatcher {
pub struct PriodMatcher(VecMatcher);
impl PriodMatcher {
pub fn new(pattern: &[MacTree]) -> Self {
pub async fn new(pattern: &[MacTree], i: &Interner) -> OrcRes<Self> {
assert!(
pattern.first().and_then(vec_attrs).is_some() && pattern.last().and_then(vec_attrs).is_some(),
"Prioritized matchers must start and end with a vectorial",
);
Self(mk_vec(pattern))
Ok(Self(mk_vec(pattern, i).await?))
}
/// tokens before the offset always match the prefix
pub fn apply<'a>(

View File

@@ -20,8 +20,6 @@ pub fn scal_match<'a>(
Some(MatchState::from_ph(key.clone(), StateEntry::Scalar(expr))),
(ScalMatcher::S(c1, b_mat), MacTok::S(c2, body)) if c1 == c2 =>
any_match(b_mat, &body[..], save_loc),
(ScalMatcher::Lambda(arg_mat, b_mat), MacTok::Lambda(arg, body)) =>
Some(any_match(arg_mat, arg, save_loc)?.combine(any_match(b_mat, body, save_loc)?)),
_ => None,
}
}

View File

@@ -11,7 +11,6 @@ use orchid_base::tokens::{PARENS, Paren};
pub enum ScalMatcher {
Name(Sym),
S(Paren, Box<AnyMatcher>),
Lambda(Box<AnyMatcher>, Box<AnyMatcher>),
Placeh { key: Tok<String> },
}
@@ -62,7 +61,6 @@ impl fmt::Display for ScalMatcher {
let (l, r, _) = PARENS.iter().find(|r| r.2 == *t).unwrap();
write!(f, "{l}{body}{r}")
},
Self::Lambda(arg, body) => write!(f, "\\{arg}.{body}"),
}
}
}