New macro system and stdlib additions
This commit is contained in:
@@ -5,10 +5,9 @@ use std::rc::Rc;
|
||||
use futures::FutureExt;
|
||||
use futures::future::join_all;
|
||||
use hashbrown::HashSet;
|
||||
use itertools::Itertools;
|
||||
use orchid_api_derive::Coding;
|
||||
use orchid_base::error::{OrcErrv, Reporter, mk_errv};
|
||||
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants, fmt};
|
||||
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;
|
||||
@@ -16,16 +15,89 @@ use orchid_base::tl_cache;
|
||||
use orchid_base::tree::{Paren, indent};
|
||||
use orchid_extension::atom::Atomic;
|
||||
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant};
|
||||
use orchid_extension::conv::ToExpr;
|
||||
use orchid_extension::expr::Expr;
|
||||
use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, sym_ref};
|
||||
use orchid_extension::system::SysCtx;
|
||||
use substack::Substack;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LowerCtx<'a> {
|
||||
pub sys: SysCtx,
|
||||
pub rep: &'a Reporter,
|
||||
fn union_rc_sets(seq: impl IntoIterator<Item = Rc<HashSet<Sym>>>) -> Rc<HashSet<Sym>> {
|
||||
let mut acc = Rc::<HashSet<Sym>>::default();
|
||||
for right in seq {
|
||||
if acc.is_empty() {
|
||||
acc = right;
|
||||
continue;
|
||||
}
|
||||
if right.is_empty() {
|
||||
continue;
|
||||
}
|
||||
acc = match (Rc::try_unwrap(acc), Rc::try_unwrap(right)) {
|
||||
(Ok(mut left), Ok(right)) => {
|
||||
left.extend(right);
|
||||
Rc::new(left)
|
||||
},
|
||||
(Ok(mut owned), Err(borrowed)) | (Err(borrowed), Ok(mut owned)) => {
|
||||
owned.extend(borrowed.iter().cloned());
|
||||
Rc::new(owned)
|
||||
},
|
||||
(Err(left), Err(right)) => Rc::new(left.union(&right).cloned().collect()),
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacTreeSeq {
|
||||
pub items: Rc<Vec<MacTree>>,
|
||||
pub top_glossary: Rc<HashSet<Sym>>,
|
||||
pub glossary: Rc<HashSet<Sym>>,
|
||||
}
|
||||
impl MacTreeSeq {
|
||||
pub fn new(i: impl IntoIterator<Item = MacTree>) -> Self {
|
||||
let mut items = Vec::new();
|
||||
let mut top_glossary = HashSet::new();
|
||||
let mut glossary = HashSet::new();
|
||||
for item in i {
|
||||
glossary.extend(item.glossary().iter().cloned());
|
||||
if let MacTok::Name(n) = item.tok() {
|
||||
top_glossary.insert(n.clone());
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
Self { items: Rc::new(items), top_glossary: Rc::new(top_glossary), glossary: Rc::new(glossary) }
|
||||
}
|
||||
pub fn map<F: FnMut(MacTree) -> Option<MacTree>>(&self, changed: &mut bool, map: &mut F) -> Self {
|
||||
Self::new(self.items.iter().map(|tree| ro(changed, |changed| tree.map(changed, map))))
|
||||
}
|
||||
pub fn glossary(&self) -> &HashSet<Sym> { &self.glossary }
|
||||
pub fn concat(self, other: Self) -> Self {
|
||||
if self.items.is_empty() {
|
||||
return other;
|
||||
} else if other.items.is_empty() {
|
||||
return self;
|
||||
}
|
||||
let items = match (Rc::try_unwrap(self.items), Rc::try_unwrap(other.items)) {
|
||||
(Ok(mut left), Ok(mut right)) => {
|
||||
left.append(&mut right);
|
||||
left
|
||||
},
|
||||
(Ok(mut left), Err(right)) => {
|
||||
left.extend_from_slice(&right[..]);
|
||||
left
|
||||
},
|
||||
(Err(left), Ok(mut right)) => {
|
||||
right.splice(0..0, left.iter().cloned());
|
||||
right
|
||||
},
|
||||
(Err(left), Err(right)) => left.iter().chain(&right[..]).cloned().collect(),
|
||||
};
|
||||
Self {
|
||||
items: Rc::new(items),
|
||||
top_glossary: union_rc_sets([self.top_glossary, other.top_glossary]),
|
||||
glossary: union_rc_sets([self.glossary, other.glossary]),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Format for MacTreeSeq {
|
||||
async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||
mtreev_fmt(&self.items[..], c).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -38,66 +110,21 @@ impl MacTree {
|
||||
pub fn tok(&self) -> &MacTok { &self.tok }
|
||||
pub fn pos(&self) -> Pos { self.pos.clone() }
|
||||
pub fn glossary(&self) -> &HashSet<Sym> { &self.glossary }
|
||||
pub async fn lower(&self, ctx: LowerCtx<'_>, args: Substack<'_, Sym>) -> GExpr {
|
||||
let expr = match self.tok() {
|
||||
MacTok::Bottom(e) => bot(e.clone()),
|
||||
MacTok::Lambda(arg, body) => {
|
||||
let MacTok::Name(name) = &*arg.tok else {
|
||||
return bot(mk_errv(
|
||||
ctx.sys.i().i("Syntax error after macros").await,
|
||||
"This token ends up as a binding, consider replacing it with a name",
|
||||
[arg.pos()],
|
||||
));
|
||||
};
|
||||
let arg_pos = args.len() as u64;
|
||||
let args = args.push(name.clone());
|
||||
let body = match &body[..] {
|
||||
[] => bot(mk_errv(
|
||||
ctx.sys.i().i("Empty lambda body").await,
|
||||
"Lambdas must evaluate to an expression",
|
||||
[self.pos()],
|
||||
)),
|
||||
[f, argv @ ..] => call(
|
||||
f.lower(ctx.clone(), args.clone()).boxed_local().await,
|
||||
lower_v(argv, ctx, args).await,
|
||||
),
|
||||
};
|
||||
lambda(arg_pos, body)
|
||||
pub fn map<F: FnMut(Self) -> Option<Self>>(&self, changed: &mut bool, map: &mut F) -> Self {
|
||||
let tok = match map(self.clone()) {
|
||||
Some(new_tok) => {
|
||||
*changed = true;
|
||||
return new_tok;
|
||||
},
|
||||
MacTok::Name(name) => match args.iter().enumerate().find(|(_, n)| *n == name) {
|
||||
None => sym_ref(name.clone()),
|
||||
Some((i, _)) => arg((args.len() - i - 1) as u64),
|
||||
None => match &*self.tok {
|
||||
MacTok::Lambda(arg, body) =>
|
||||
MacTok::Lambda(ro(changed, |changed| arg.map(changed, map)), body.map(changed, map)),
|
||||
MacTok::Name(_) | MacTok::Value(_) => return self.clone(),
|
||||
MacTok::Slot | MacTok::Ph(_) | MacTok::Bottom(_) => return self.clone(),
|
||||
MacTok::S(p, body) => MacTok::S(*p, body.map(changed, map)),
|
||||
},
|
||||
MacTok::Ph(ph) => {
|
||||
return bot(mk_errv(
|
||||
ctx.sys.i().i("Placeholder in value").await,
|
||||
format!("Placeholder {ph} is only supported in macro patterns"),
|
||||
[self.pos()],
|
||||
));
|
||||
},
|
||||
MacTok::S(Paren::Round, body) => match &body[..] {
|
||||
[fun, argv @ ..] => call(
|
||||
fun.lower(ctx.clone(), args.clone()).boxed_local().await,
|
||||
lower_v(argv, ctx, args).await,
|
||||
),
|
||||
[] =>
|
||||
return bot(mk_errv(
|
||||
ctx.sys.i().i("Empty ()").await,
|
||||
"Empty () is not a meaningful expression",
|
||||
[self.pos()],
|
||||
)),
|
||||
},
|
||||
MacTok::S(..) => {
|
||||
return bot(mk_errv(
|
||||
ctx.sys.i().i("[] or {} after macros").await,
|
||||
format!("{} didn't match any macro", fmt(self, ctx.sys.i()).await),
|
||||
[self.pos()],
|
||||
));
|
||||
},
|
||||
MacTok::Slot => panic!("Uninstantiated template should never be exposed"),
|
||||
MacTok::Value(v) => v.clone().to_expr().await,
|
||||
};
|
||||
expr.at(self.pos())
|
||||
if *changed { tok.at(self.pos()) } else { self.clone() }
|
||||
}
|
||||
}
|
||||
impl Atomic for MacTree {
|
||||
@@ -119,35 +146,31 @@ impl Format for MacTree {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn lower_v(v: &[MacTree], ctx: LowerCtx<'_>, args: Substack<'_, Sym>) -> Vec<GExpr> {
|
||||
join_all(v.iter().map(|t| t.lower(ctx.clone(), args.clone())).collect::<Vec<_>>()).await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MacTok {
|
||||
S(Paren, Vec<MacTree>),
|
||||
S(Paren, MacTreeSeq),
|
||||
Name(Sym),
|
||||
/// Only permitted in arguments to `instantiate_tpl`
|
||||
Slot,
|
||||
Value(Expr),
|
||||
Lambda(MacTree, Vec<MacTree>),
|
||||
Lambda(MacTree, MacTreeSeq),
|
||||
/// 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 build_glossary(&self) -> HashSet<Sym> {
|
||||
pub fn build_glossary(&self) -> Rc<HashSet<Sym>> {
|
||||
match self {
|
||||
MacTok::Bottom(_) | MacTok::Ph(_) | MacTok::Slot | MacTok::Value(_) => HashSet::new(),
|
||||
MacTok::Name(sym) => HashSet::from([sym.clone()]),
|
||||
MacTok::S(_, body) => body.iter().flat_map(|mt| &*mt.glossary).cloned().collect(),
|
||||
MacTok::Bottom(_) | MacTok::Ph(_) | MacTok::Slot | MacTok::Value(_) => Rc::default(),
|
||||
MacTok::Name(sym) => Rc::new(HashSet::from([sym.clone()])),
|
||||
MacTok::S(_, body) => union_rc_sets(body.items.iter().map(|mt| mt.glossary.clone())),
|
||||
MacTok::Lambda(arg, body) =>
|
||||
body.iter().chain([arg]).flat_map(|mt| &*mt.glossary).cloned().collect(),
|
||||
union_rc_sets(body.items.iter().chain([arg]).map(|mt| mt.glossary.clone())),
|
||||
}
|
||||
}
|
||||
pub fn at(self, pos: impl Into<Pos>) -> MacTree {
|
||||
MacTree { pos: pos.into(), glossary: Rc::new(self.build_glossary()), tok: Rc::new(self) }
|
||||
MacTree { pos: pos.into(), glossary: self.build_glossary(), tok: Rc::new(self) }
|
||||
}
|
||||
}
|
||||
impl Format for MacTok {
|
||||
@@ -157,7 +180,7 @@ impl Format for MacTok {
|
||||
Self::Lambda(arg, b) => tl_cache!(Rc<Variants>: Rc::new(Variants::default()
|
||||
.unbounded("\\{0} {1l}")
|
||||
.bounded("(\\{0} {1b})")))
|
||||
.units([arg.print(c).boxed_local().await, mtreev_fmt(b, c).await]),
|
||||
.units([arg.print(c).boxed_local().await, b.print(c).await]),
|
||||
Self::Name(n) => format!("{n}").into(),
|
||||
Self::Ph(ph) => format!("{ph}").into(),
|
||||
Self::S(p, body) => match *p {
|
||||
@@ -165,7 +188,7 @@ impl Format for MacTok {
|
||||
Paren::Curly => tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("{{0b}}"))),
|
||||
Paren::Square => tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("[{0b}]"))),
|
||||
}
|
||||
.units([mtreev_fmt(body, c).await]),
|
||||
.units([body.print(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(),
|
||||
@@ -177,7 +200,7 @@ pub async fn mtreev_fmt<'b>(
|
||||
v: impl IntoIterator<Item = &'b MacTree>,
|
||||
c: &(impl FmtCtx + ?Sized),
|
||||
) -> FmtUnit {
|
||||
FmtUnit::sequence(" ", None, join_all(v.into_iter().map(|t| t.print(c))).await)
|
||||
FmtUnit::sequence("", " ", "", None, join_all(v.into_iter().map(|t| t.print(c))).await)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
@@ -203,36 +226,6 @@ pub enum PhKind {
|
||||
Vector { at_least_one: bool, priority: u8 },
|
||||
}
|
||||
|
||||
pub fn map_mactree<F: FnMut(MacTree) -> Option<MacTree>>(
|
||||
src: &MacTree,
|
||||
changed: &mut bool,
|
||||
map: &mut F,
|
||||
) -> MacTree {
|
||||
let tok = match map(src.clone()) {
|
||||
Some(new_tok) => {
|
||||
*changed = true;
|
||||
return new_tok;
|
||||
},
|
||||
None => match &*src.tok {
|
||||
MacTok::Lambda(arg, body) => MacTok::Lambda(
|
||||
ro(changed, |changed| map_mactree(arg, changed, map)),
|
||||
map_mactree_v(body, changed, map),
|
||||
),
|
||||
MacTok::Name(_) | MacTok::Value(_) => return src.clone(),
|
||||
MacTok::Slot | MacTok::Ph(_) | MacTok::Bottom(_) => return src.clone(),
|
||||
MacTok::S(p, body) => MacTok::S(*p, map_mactree_v(body, changed, map)),
|
||||
},
|
||||
};
|
||||
if *changed { tok.at(src.pos()) } else { src.clone() }
|
||||
}
|
||||
pub fn map_mactree_v<F: FnMut(MacTree) -> Option<MacTree>>(
|
||||
src: &[MacTree],
|
||||
changed: &mut bool,
|
||||
map: &mut F,
|
||||
) -> Vec<MacTree> {
|
||||
src.iter().map(|tree| ro(changed, |changed| map_mactree(tree, changed, map))).collect_vec()
|
||||
}
|
||||
|
||||
/// reverse "or". Inside, the flag is always false, but raising it will raise
|
||||
/// the outside flag too.
|
||||
fn ro<T>(flag: &mut bool, cb: impl FnOnce(&mut bool) -> T) -> T {
|
||||
@@ -241,7 +234,3 @@ fn ro<T>(flag: &mut bool, cb: impl FnOnce(&mut bool) -> T) -> T {
|
||||
*flag |= new_flag;
|
||||
val
|
||||
}
|
||||
|
||||
pub fn glossary_v(src: &[MacTree]) -> impl Iterator<Item = Sym> {
|
||||
src.iter().flat_map(|mt| mt.glossary()).cloned()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user