forked from Orchid/orchid
Macro system done in theory
too afraid to begin debugging, resting for a moment
This commit is contained in:
@@ -4,3 +4,6 @@ mod std;
|
||||
pub use std::number::num_atom::{Float, HomoArray, Int, Num};
|
||||
pub use std::std_system::StdSystem;
|
||||
pub use std::string::str_atom::OrcString;
|
||||
|
||||
pub use macros::macro_system::MacroSystem;
|
||||
pub use macros::mactree::{MacTok, MacTree};
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use std::borrow::Cow;
|
||||
use std::pin::Pin;
|
||||
|
||||
use futures::AsyncWrite;
|
||||
use never::Never;
|
||||
use orchid_extension::atom::{Atomic, TypAtom};
|
||||
use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant, get_own_instance};
|
||||
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own};
|
||||
use orchid_extension::conv::{ToExpr, TryFromExpr};
|
||||
use orchid_extension::expr::Expr;
|
||||
use orchid_extension::gen_expr::GExpr;
|
||||
use orchid_extension::system::SysCtx;
|
||||
|
||||
use crate::macros::mactree::{MacTok, MacTree, map_mactree};
|
||||
|
||||
@@ -24,16 +21,7 @@ impl Atomic for InstantiateTplCall {
|
||||
}
|
||||
impl OwnedAtom for InstantiateTplCall {
|
||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||
/// TODO: get serialization done for mactree
|
||||
type Refs = Vec<Expr>;
|
||||
async fn serialize(
|
||||
&self,
|
||||
ctx: SysCtx,
|
||||
write: Pin<&mut (impl AsyncWrite + ?Sized)>,
|
||||
) -> Self::Refs {
|
||||
todo!()
|
||||
}
|
||||
async fn deserialize(ctx: impl DeserializeCtx, refs: Self::Refs) -> Self { todo!() }
|
||||
type Refs = Never;
|
||||
// Technically must be supported but shouldn't actually ever be called
|
||||
async fn call_ref(&self, arg: Expr) -> GExpr {
|
||||
eprintln!(
|
||||
@@ -43,20 +31,19 @@ impl OwnedAtom for InstantiateTplCall {
|
||||
self.clone().call(arg).await
|
||||
}
|
||||
async fn call(mut self, arg: Expr) -> GExpr {
|
||||
let arg = match TypAtom::try_from_expr(arg).await {
|
||||
Err(e) => return Err::<Never, _>(e).to_expr(),
|
||||
Ok(t) => get_own_instance(t).await,
|
||||
match TypAtom::<MacTree>::try_from_expr(arg).await {
|
||||
Err(e) => return Err::<Never, _>(e).to_expr().await,
|
||||
Ok(t) => self.argv.push(own(t).await),
|
||||
};
|
||||
self.argv.push(arg);
|
||||
if self.argv.len() < self.argc {
|
||||
return self.to_expr();
|
||||
return self.to_expr().await;
|
||||
}
|
||||
let mut args = self.argv.into_iter();
|
||||
map_mactree(&self.tpl, &mut false, &mut |tpl| match &*tpl.tok {
|
||||
MacTok::Ph(_) => panic!("instantiate_tpl received a placeholder"),
|
||||
MacTok::Slot => Some(args.next().expect("Not enough arguments to fill all slots!")),
|
||||
let ret = map_mactree(&self.tpl, &mut false, &mut |mt| match mt.tok() {
|
||||
MacTok::Slot => Some(args.next().expect("Not enough arguments to fill all slots")),
|
||||
_ => None,
|
||||
})
|
||||
.to_expr()
|
||||
});
|
||||
assert!(args.next().is_none(), "Too many arguments for all slots");
|
||||
ret.to_expr().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
use std::pin::pin;
|
||||
|
||||
use async_std::stream;
|
||||
use async_stream::stream;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use orchid_base::error::OrcRes;
|
||||
use orchid_api::Paren;
|
||||
use orchid_base::error::{OrcRes, Reporter};
|
||||
use orchid_base::name::Sym;
|
||||
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 orchid_base::sym;
|
||||
use orchid_extension::gen_expr::{atom, call, sym_ref};
|
||||
use orchid_extension::parser::{ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser};
|
||||
|
||||
use crate::macros::mactree::{MacTok, MacTree, map_mactree_v};
|
||||
use crate::macros::mactree::{MacTok, MacTree, glossary_v, map_mactree_v};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LetLine;
|
||||
@@ -23,6 +26,7 @@ impl Parser for LetLine {
|
||||
comments: Vec<Comment>,
|
||||
line: PSnippet<'a>,
|
||||
) -> OrcRes<Vec<ParsedLine>> {
|
||||
let sr = line.sr();
|
||||
let Parsed { output: name_tok, tail } = try_pop_no_fluff(&ctx, line).await?;
|
||||
let Some(name) = name_tok.as_name() else {
|
||||
let err = token_errv(&ctx, name_tok, "Constant must have a name", |t| {
|
||||
@@ -31,44 +35,41 @@ impl Parser for LetLine {
|
||||
return Err(err.await);
|
||||
};
|
||||
let Parsed { tail, .. } = expect_tok(&ctx, tail, ctx.i().i("=").await).await?;
|
||||
let mut names = HashMap::new();
|
||||
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.clone());
|
||||
Ok(vec![ParsedLine::cnst(&line.sr(), &comments, exported, name, async move |ctx| {
|
||||
let rep = Reporter::new();
|
||||
let dealiased = dealias_mac_v(aliased, &ctx, &rep).await;
|
||||
let macro_input = MacTok::S(Paren::Round, dealiased).at(sr.pos());
|
||||
if let Some(e) = rep.errv() {
|
||||
return Err(e);
|
||||
}
|
||||
None
|
||||
});
|
||||
Ok(vec![ParsedLine {
|
||||
comments,
|
||||
sr: line.sr(),
|
||||
kind: ParsedLineKind::Mem(ParsedMem {
|
||||
exported,
|
||||
name,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
.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")
|
||||
}),
|
||||
}),
|
||||
}])
|
||||
Ok(call([
|
||||
sym_ref(sym!(macros::lower; ctx.i()).await),
|
||||
call([sym_ref(sym!(macros::resolve; ctx.i()).await), atom(macro_input)]),
|
||||
]))
|
||||
})])
|
||||
}
|
||||
}
|
||||
|
||||
async fn parse_tokv(line: PSnippet<'_>, ctx: &ParsCtx<'_>) -> Vec<MacTree> {
|
||||
pub async fn dealias_mac_v(aliased: Vec<MacTree>, ctx: &ConstCtx, rep: &Reporter) -> Vec<MacTree> {
|
||||
let keys = glossary_v(&aliased).collect_vec();
|
||||
let mut names: HashMap<_, _> = HashMap::new();
|
||||
let mut stream = pin!(ctx.names(&keys).zip(stream::from_iter(&keys)));
|
||||
while let Some((canonical, local)) = stream.next().await {
|
||||
match canonical {
|
||||
Err(e) => rep.report(e),
|
||||
Ok(name) => {
|
||||
names.insert(local.clone(), name);
|
||||
},
|
||||
}
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn parse_tokv(line: PSnippet<'_>, ctx: &impl ParseCtx) -> 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();
|
||||
@@ -89,15 +90,15 @@ async fn parse_tokv(line: PSnippet<'_>, ctx: &ParsCtx<'_>) -> Vec<MacTree> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn parse_tokv_no_lambdas(line: &[PTokTree], ctx: &ParsCtx<'_>) -> Vec<MacTree> {
|
||||
async fn parse_tokv_no_lambdas(line: &[PTokTree], ctx: &impl ParseCtx) -> 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> {
|
||||
pub async fn parse_tok(tree: &PTokTree, ctx: &impl ParseCtx) -> 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::Name(n) => MacTok::Name(Sym::new([n.clone()], ctx.i()).await.unwrap()),
|
||||
PTok::NS(..) => match tree.as_multiname() {
|
||||
Ok(mn) => MacTok::Name(mn.to_sym(ctx.i()).await),
|
||||
Err(nested) => {
|
||||
|
||||
@@ -1,20 +1,83 @@
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use orchid_base::error::Reporter;
|
||||
use orchid_base::sym;
|
||||
use orchid_extension::atom::TypAtom;
|
||||
use orchid_extension::atom_owned::get_own_instance;
|
||||
use orchid_extension::atom_owned::own;
|
||||
use orchid_extension::conv::ToExpr;
|
||||
use orchid_extension::coroutine_exec::exec;
|
||||
use orchid_extension::gen_expr::{atom, call, sym_ref};
|
||||
use orchid_extension::reflection::{ReflMemKind, refl};
|
||||
use orchid_extension::tree::{GenMember, comments, fun, prefix};
|
||||
use substack::Substack;
|
||||
|
||||
use crate::Int;
|
||||
use crate::macros::instantiate_tpl::InstantiateTplCall;
|
||||
use crate::macros::mactree::MacTree;
|
||||
use crate::macros::macro_line::{Macro, Matcher};
|
||||
use crate::macros::mactree::{LowerCtx, MacTree};
|
||||
use crate::macros::recur_state::RecurState;
|
||||
use crate::macros::resolve::{ResolveCtx, resolve};
|
||||
|
||||
pub fn gen_macro_lib() -> Vec<GenMember> {
|
||||
prefix("macros", [comments(
|
||||
["This is an internal function, you can't obtain a value of its argument type.", "hidden"],
|
||||
fun(true, "instantiate_tpl", |tpl: TypAtom<MacTree>, right: Int| async move {
|
||||
InstantiateTplCall {
|
||||
tpl: get_own_instance(tpl).await,
|
||||
argc: right.0.try_into().unwrap(),
|
||||
argv: Vec::new(),
|
||||
}
|
||||
prefix("macros", [
|
||||
comments(
|
||||
["This is an internal function, you can't obtain a value of its argument type.", "hidden"],
|
||||
fun(true, "instantiate_tpl", |tpl: TypAtom<MacTree>, right: Int| async move {
|
||||
InstantiateTplCall {
|
||||
tpl: own(tpl).await,
|
||||
argc: right.0.try_into().unwrap(),
|
||||
argv: Vec::new(),
|
||||
}
|
||||
}),
|
||||
),
|
||||
fun(true, "resolve", |tpl: TypAtom<MacTree>| async move {
|
||||
call([
|
||||
sym_ref(sym!(macro::resolve_recur; tpl.untyped.ctx().i()).await),
|
||||
atom(RecurState::Bottom),
|
||||
tpl.untyped.ex().to_expr().await,
|
||||
])
|
||||
}),
|
||||
)])
|
||||
fun(true, "lower", |tpl: TypAtom<MacTree>| async move {
|
||||
let ctx = LowerCtx { sys: tpl.untyped.ctx().clone(), rep: &Reporter::new() };
|
||||
let res = own(tpl).await.lower(ctx, Substack::Bottom).await;
|
||||
if let Some(e) = Reporter::new().errv() { Err(e) } else { Ok(res) }
|
||||
}),
|
||||
fun(true, "resolve_recur", |state: TypAtom<RecurState>, tpl: TypAtom<MacTree>| async move {
|
||||
exec(async move |mut h| {
|
||||
let ctx = tpl.ctx().clone();
|
||||
let root = refl(&ctx);
|
||||
let tpl = own(tpl.clone()).await;
|
||||
let mut macros = HashMap::new();
|
||||
for n in tpl.glossary() {
|
||||
if let Ok(ReflMemKind::Const) = root.get_by_path(n).await.map(|m| m.kind()) {
|
||||
let Ok(mac) = h.exec::<TypAtom<Macro>>(sym_ref(n.clone())).await else { continue };
|
||||
let mac = own(mac).await;
|
||||
macros.entry(mac.0.own_kws[0].clone()).or_insert(mac);
|
||||
}
|
||||
}
|
||||
let mut named = HashMap::new();
|
||||
let mut priod = Vec::new();
|
||||
for (_, mac) in macros.iter() {
|
||||
for rule in mac.0.rules.iter() {
|
||||
if rule.glossary.is_subset(tpl.glossary()) {
|
||||
match &rule.pattern {
|
||||
Matcher::Named(m) =>
|
||||
named.entry(m.head()).or_insert(Vec::new()).push((m, mac, rule)),
|
||||
Matcher::Priod(p) => priod.push((mac.0.prio, (p, mac, rule))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let priod = priod.into_iter().sorted_unstable_by_key(|(p, _)| *p).map(|(_, r)| r).collect();
|
||||
let mut rctx = ResolveCtx { h, recur: own(state).await, ctx: ctx.clone(), named, priod };
|
||||
let resolve_res = resolve(&mut rctx, &tpl).await;
|
||||
std::mem::drop(rctx);
|
||||
match resolve_res {
|
||||
Some(out_tree) => out_tree.to_expr().await,
|
||||
None => tpl.to_expr().await,
|
||||
}
|
||||
})
|
||||
.await
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
230
orchid-std/src/macros/macro_line.rs
Normal file
230
orchid-std/src/macros/macro_line.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use async_once_cell::OnceCell;
|
||||
use async_std::stream;
|
||||
use futures::StreamExt;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use itertools::Itertools;
|
||||
use never::Never;
|
||||
use orchid_api::Paren;
|
||||
use orchid_base::error::{OrcRes, Reporter, mk_errv};
|
||||
use orchid_base::interner::Tok;
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::parse::{
|
||||
Comment, ParseCtx, Parsed, Snippet, expect_end, expect_tok, line_items, token_errv,
|
||||
try_pop_no_fluff,
|
||||
};
|
||||
use orchid_base::tree::Token;
|
||||
use orchid_base::{clone, sym};
|
||||
use orchid_extension::atom::{Atomic, TypAtom};
|
||||
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant};
|
||||
use orchid_extension::conv::{ToExpr, TryFromExpr};
|
||||
use orchid_extension::gen_expr::{atom, call, sym_ref};
|
||||
use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser};
|
||||
|
||||
use crate::macros::let_line::{dealias_mac_v, parse_tokv};
|
||||
use crate::macros::mactree::{glossary_v, map_mactree_v};
|
||||
use crate::macros::recur_state::{RecurState, RulePath};
|
||||
use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher};
|
||||
use crate::{Int, MacTok};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MacroLine;
|
||||
impl Parser for MacroLine {
|
||||
const LINE_HEAD: &'static str = "macro";
|
||||
async fn parse<'a>(
|
||||
ctx: ParsCtx<'a>,
|
||||
exported: bool,
|
||||
comments: Vec<Comment>,
|
||||
line: PSnippet<'a>,
|
||||
) -> OrcRes<Vec<ParsedLine>> {
|
||||
if exported {
|
||||
return Err(mk_errv(
|
||||
ctx.i().i("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, tail } = try_pop_no_fluff(&ctx, line).await?;
|
||||
let bad_first_item_err = || {
|
||||
token_errv(&ctx, output, "Expected priority or block", |s| {
|
||||
format!("Expected a priority number or a () block, found {s}")
|
||||
})
|
||||
};
|
||||
let (prio, body) = match &output.tok {
|
||||
Token::S(Paren::Round, body) => (None, body),
|
||||
Token::Handle(expr) => match TypAtom::<Int>::try_from_expr(expr.clone()).await {
|
||||
Err(e) => {
|
||||
return Err(e + bad_first_item_err().await);
|
||||
},
|
||||
Ok(prio) => {
|
||||
let Token::S(Paren::Round, block) = &output.tok else {
|
||||
return Err(
|
||||
token_errv(&ctx, output, "Expected () block", |s| {
|
||||
format!("Expected a () block, found {s}")
|
||||
})
|
||||
.await,
|
||||
);
|
||||
};
|
||||
(Some(prio), block)
|
||||
},
|
||||
},
|
||||
_ => return Err(bad_first_item_err().await),
|
||||
};
|
||||
expect_end(&ctx, tail).await?;
|
||||
let lines = line_items(&ctx, Snippet::new(output, body)).await;
|
||||
let Some((kw_line, rule_lines)) = lines.split_first() else { return Ok(Vec::new()) };
|
||||
let mut keywords = HashMap::new();
|
||||
let Parsed { tail: kw_tail, .. } =
|
||||
expect_tok(&ctx, kw_line.tail, ctx.i().i("keywords").await).await?;
|
||||
for kw_tok in kw_tail.iter().filter(|kw| !kw.is_fluff()) {
|
||||
match kw_tok.as_name() {
|
||||
Some(kw) => {
|
||||
keywords.insert(kw, kw_tok.sr());
|
||||
},
|
||||
None => ctx.rep().report(
|
||||
token_errv(&ctx, kw_tok, "invalid macro keywords list", |tok| {
|
||||
format!("The keywords list must be a sequence of names; received {tok}")
|
||||
})
|
||||
.await,
|
||||
),
|
||||
}
|
||||
}
|
||||
let Some(macro_name) = keywords.keys().next().cloned() else {
|
||||
return Err(mk_errv(
|
||||
ctx.i().i("macro with no keywords").await,
|
||||
"Macros must define at least one macro of their own.",
|
||||
[kw_line.tail.sr()],
|
||||
));
|
||||
};
|
||||
let mut rules = Vec::new();
|
||||
let mut lines = Vec::new();
|
||||
for (idx, line) in rule_lines.iter().enumerate().map(|(n, v)| (n as u32, v)) {
|
||||
let path = RulePath { module: module.clone(), main_kw: macro_name.clone(), rule: idx };
|
||||
let sr = line.tail.sr();
|
||||
let name = ctx.i().i(&path.name()).await;
|
||||
let Parsed { tail, .. } = expect_tok(&ctx, line.tail, ctx.i().i("rule").await).await?;
|
||||
let arrow_token = ctx.i().i("=>").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,
|
||||
"Rule lines are of the form `rule ...pattern => ...body`",
|
||||
[line.tail.sr()],
|
||||
));
|
||||
continue;
|
||||
};
|
||||
let pattern = parse_tokv(pattern, &ctx).await;
|
||||
let mut placeholders = Vec::new();
|
||||
map_mactree_v(&pattern, &mut false, &mut |tok| {
|
||||
if let MacTok::Ph(ph) = tok.tok() {
|
||||
placeholders.push((ph.clone(), tok.pos()))
|
||||
}
|
||||
None
|
||||
});
|
||||
let mut body_mactree = parse_tokv(body, &ctx).await;
|
||||
for (ph, ph_pos) in placeholders.iter().rev() {
|
||||
let name = ctx.module().suffix([ph.name.clone()], ctx.i()).await;
|
||||
body_mactree = vec![
|
||||
MacTok::Lambda(MacTok::Name(name).at(ph_pos.clone()), body_mactree).at(ph_pos.clone()),
|
||||
]
|
||||
}
|
||||
let body_sr = body.sr();
|
||||
rules.push((name.clone(), placeholders, rules.len() as u32, sr.pos(), 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!(macro::resolve_recur; ctx.i()).await),
|
||||
atom(RecurState::base(path)),
|
||||
macro_input.to_expr().await,
|
||||
]))
|
||||
}))
|
||||
}
|
||||
let mac_cell = Rc::new(OnceCell::new());
|
||||
let keywords = Rc::new(keywords);
|
||||
let rules = Rc::new(RefCell::new(Some(rules)));
|
||||
for (kw, sr) in &*keywords {
|
||||
clone!(mac_cell, keywords, rules, module, prio);
|
||||
lines.push(ParsedLine::cnst(&sr.clone(), &comments, true, kw.clone(), async move |cctx| {
|
||||
let mac = mac_cell
|
||||
.get_or_init(async {
|
||||
let rep = Reporter::new();
|
||||
let rules = rules.borrow_mut().take().expect("once cell initializer runs");
|
||||
let rules = stream::from_iter(rules)
|
||||
.then(|(body_name, placeholders, index, pos, pattern_macv)| {
|
||||
let cctx = &cctx;
|
||||
let rep = &rep;
|
||||
let prio = &prio;
|
||||
async move {
|
||||
let pattern_abs = dealias_mac_v(pattern_macv, cctx, rep).await;
|
||||
let glossary = glossary_v(&pattern_abs).collect();
|
||||
let pattern_res = match prio {
|
||||
None => NamedMatcher::new(&pattern_abs, cctx.i()).await.map(Matcher::Named),
|
||||
Some(_) => PriodMatcher::new(&pattern_abs, cctx.i()).await.map(Matcher::Priod),
|
||||
};
|
||||
let placeholders = placeholders.into_iter().map(|(ph, _)| ph.name).collect_vec();
|
||||
match pattern_res {
|
||||
Ok(pattern) =>
|
||||
Some(Rule { index, pos, body_name, pattern, glossary, placeholders }),
|
||||
Err(e) => {
|
||||
rep.report(e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
.flat_map(stream::from_iter)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
let own_kws = keywords.keys().cloned().collect_vec();
|
||||
Macro(Rc::new(MacroData { module, prio: prio.map(|i| i.0 as u64), rules, own_kws }))
|
||||
})
|
||||
.await;
|
||||
atom(mac.clone())
|
||||
}))
|
||||
}
|
||||
Ok(lines)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MacroData {
|
||||
pub module: Sym,
|
||||
pub prio: Option<u64>,
|
||||
pub rules: Vec<Rule>,
|
||||
pub own_kws: Vec<Tok<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Macro(pub Rc<MacroData>);
|
||||
#[derive(Debug)]
|
||||
pub struct Rule {
|
||||
pub index: u32,
|
||||
pub pos: Pos,
|
||||
pub pattern: Matcher,
|
||||
pub glossary: HashSet<Sym>,
|
||||
pub placeholders: Vec<Tok<String>>,
|
||||
pub body_name: Tok<String>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum Matcher {
|
||||
Named(NamedMatcher),
|
||||
Priod(PriodMatcher),
|
||||
}
|
||||
impl Atomic for Macro {
|
||||
type Data = ();
|
||||
type Variant = OwnedVariant;
|
||||
}
|
||||
impl OwnedAtom for Macro {
|
||||
type Refs = Never;
|
||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use never::Never;
|
||||
use orchid_base::interner::Interner;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::reqnot::Receipt;
|
||||
use orchid_extension::atom::AtomDynfo;
|
||||
use orchid_extension::atom::{AtomDynfo, AtomicFeatures};
|
||||
use orchid_extension::entrypoint::ExtReq;
|
||||
use orchid_extension::lexer::LexerObj;
|
||||
use orchid_extension::parser::ParserObj;
|
||||
@@ -10,14 +10,18 @@ use orchid_extension::system::{System, SystemCard};
|
||||
use orchid_extension::system_ctor::SystemCtor;
|
||||
use orchid_extension::tree::GenMember;
|
||||
|
||||
use crate::macros::instantiate_tpl::InstantiateTplCall;
|
||||
use crate::macros::let_line::LetLine;
|
||||
use crate::macros::macro_lib::gen_macro_lib;
|
||||
use crate::macros::macro_line::MacroLine;
|
||||
use crate::macros::mactree_lexer::MacTreeLexer;
|
||||
use crate::macros::recur_state::RecurState;
|
||||
use crate::{MacTree, StdSystem};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MacroSystem;
|
||||
impl SystemCtor for MacroSystem {
|
||||
type Deps = ();
|
||||
type Deps = StdSystem;
|
||||
type Instance = Self;
|
||||
const NAME: &'static str = "macros";
|
||||
const VERSION: f64 = 0.00_01;
|
||||
@@ -26,12 +30,14 @@ impl SystemCtor for MacroSystem {
|
||||
impl SystemCard for MacroSystem {
|
||||
type Ctor = Self;
|
||||
type Req = Never;
|
||||
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn AtomDynfo>>> { [] }
|
||||
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn AtomDynfo>>> {
|
||||
[Some(InstantiateTplCall::dynfo()), Some(MacTree::dynfo()), Some(RecurState::dynfo())]
|
||||
}
|
||||
}
|
||||
impl System for MacroSystem {
|
||||
async fn request(_: ExtReq<'_>, req: Self::Req) -> Receipt<'_> { match req {} }
|
||||
async fn prelude(_: &Interner) -> Vec<Sym> { vec![] }
|
||||
fn lexers() -> Vec<LexerObj> { vec![&MacTreeLexer] }
|
||||
fn parsers() -> Vec<ParserObj> { vec![&LetLine] }
|
||||
fn parsers() -> Vec<ParserObj> { vec![&LetLine, &MacroLine] }
|
||||
fn env() -> Vec<GenMember> { gen_macro_lib() }
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ use std::rc::Rc;
|
||||
|
||||
use futures::FutureExt;
|
||||
use futures::future::join_all;
|
||||
use hashbrown::HashSet;
|
||||
use itertools::Itertools;
|
||||
use orchid_api::Paren;
|
||||
use orchid_base::error::OrcErrv;
|
||||
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
|
||||
use orchid_base::error::{OrcErrv, Reporter, mk_errv};
|
||||
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants, fmt};
|
||||
use orchid_base::interner::Tok;
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::name::Sym;
|
||||
@@ -15,16 +16,71 @@ 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::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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacTree {
|
||||
pub pos: Pos,
|
||||
pub tok: Rc<MacTok>,
|
||||
pub glossary: Rc<HashSet<Sym>>,
|
||||
}
|
||||
impl MacTree {
|
||||
pub fn tok(&self) -> &MacTok { &*self.tok }
|
||||
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 {
|
||||
let err = 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()],
|
||||
);
|
||||
ctx.rep.report(err.clone());
|
||||
return bot(err);
|
||||
};
|
||||
lambda(args.len() as u64, lower_v(body, ctx, args.push(name.clone())).await)
|
||||
},
|
||||
MacTok::Name(name) => match args.iter().enumerate().find(|(_, n)| *n == name) {
|
||||
None => sym_ref(name.clone()),
|
||||
Some((i, _)) => arg((args.len() - i) as u64),
|
||||
},
|
||||
MacTok::Ph(ph) => {
|
||||
let err = mk_errv(
|
||||
ctx.sys.i().i("Placeholder in value").await,
|
||||
format!("Placeholder {ph} is only supported in macro patterns"),
|
||||
[self.pos()],
|
||||
);
|
||||
ctx.rep.report(err.clone());
|
||||
return bot(err);
|
||||
},
|
||||
MacTok::S(Paren::Round, body) => call(lower_v(body, ctx, args).await),
|
||||
MacTok::S(..) => {
|
||||
let err = mk_errv(
|
||||
ctx.sys.i().i("[] or {} after macros").await,
|
||||
format!("{} didn't match any macro", fmt(self, ctx.sys.i()).await),
|
||||
[self.pos()],
|
||||
);
|
||||
ctx.rep.report(err.clone());
|
||||
return bot(err);
|
||||
},
|
||||
MacTok::Slot => panic!("Uninstantiated template should never be exposed"),
|
||||
MacTok::Value(v) => v.clone().to_expr().await,
|
||||
};
|
||||
expr.at(self.pos())
|
||||
}
|
||||
}
|
||||
impl Atomic for MacTree {
|
||||
type Data = ();
|
||||
@@ -34,11 +90,20 @@ impl OwnedAtom for MacTree {
|
||||
type Refs = ();
|
||||
|
||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||
async fn print_atom<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||
self.tok.print(c).await
|
||||
}
|
||||
}
|
||||
impl Format for MacTree {
|
||||
async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||
self.tok.print(c).await
|
||||
}
|
||||
}
|
||||
|
||||
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>),
|
||||
@@ -53,8 +118,17 @@ pub enum MacTok {
|
||||
Bottom(OrcErrv),
|
||||
}
|
||||
impl MacTok {
|
||||
pub fn build_glossary(&self) -> 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::Lambda(arg, body) =>
|
||||
body.iter().chain([arg]).flat_map(|mt| &*mt.glossary).cloned().collect(),
|
||||
}
|
||||
}
|
||||
pub fn at(self, pos: impl Into<Pos>) -> MacTree {
|
||||
MacTree { pos: pos.into(), tok: Rc::new(self) }
|
||||
MacTree { pos: pos.into(), glossary: Rc::new(self.build_glossary()), tok: Rc::new(self) }
|
||||
}
|
||||
}
|
||||
impl Format for MacTok {
|
||||
@@ -77,7 +151,7 @@ impl Format for MacTok {
|
||||
},
|
||||
[mtreev_fmt(body, c).await],
|
||||
),
|
||||
Self::Slot => "SLOT".into(),
|
||||
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(),
|
||||
}
|
||||
@@ -129,12 +203,12 @@ 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(_) | MacTok::Bottom(_) =>
|
||||
return src.clone(),
|
||||
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 { MacTree { pos: src.pos.clone(), tok: Rc::new(tok) } } else { src.clone() }
|
||||
if *changed { tok.at(src.pos()) } else { src.clone() }
|
||||
}
|
||||
pub fn map_mactree_v<F: FnMut(MacTree) -> Option<MacTree>>(
|
||||
src: &[MacTree],
|
||||
@@ -152,3 +226,7 @@ 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()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use std::ops::RangeInclusive;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::FutureExt;
|
||||
use orchid_base::error::{OrcRes, mk_errv};
|
||||
use orchid_base::parse::ParseCtx;
|
||||
use orchid_base::sym;
|
||||
use orchid_base::tokens::PARENS;
|
||||
use orchid_base::tree::Paren;
|
||||
use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable};
|
||||
use orchid_extension::tree::{GenTok, GenTokTree, x_tok};
|
||||
use orchid_extension::parser::p_tree2gen;
|
||||
use orchid_extension::tree::{GenTok, GenTokTree, ref_tok, x_tok};
|
||||
|
||||
use crate::macros::let_line::parse_tok;
|
||||
use crate::macros::mactree::{MacTok, MacTree};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -15,24 +19,41 @@ impl Lexer for MacTreeLexer {
|
||||
const CHAR_FILTER: &'static [RangeInclusive<char>] = &['\''..='\''];
|
||||
async fn lex<'a>(tail: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> {
|
||||
let Some(tail2) = tail.strip_prefix('\'') else {
|
||||
return Err(err_not_applicable(ctx.i()).await.into());
|
||||
return Err(err_not_applicable(ctx.i()).await);
|
||||
};
|
||||
let tail3 = tail2.trim_start();
|
||||
return match mac_tree(tail3, ctx).await {
|
||||
Ok((tail4, mactree)) => Ok((tail4, x_tok(mactree).at(ctx.pos_tt(tail, tail4)))),
|
||||
let mut args = Vec::new();
|
||||
return match mac_tree(tail3, &mut args, ctx).await {
|
||||
Ok((tail4, mactree)) => {
|
||||
let range = ctx.pos_tt(tail, tail4);
|
||||
let tok = match &args[..] {
|
||||
[] => x_tok(mactree).await,
|
||||
_ => {
|
||||
let call = ([
|
||||
ref_tok(sym!(macros::instantiate_tpl; ctx.i()).await).await.at(range.clone()),
|
||||
x_tok(mactree).await.at(range.clone()),
|
||||
]
|
||||
.into_iter())
|
||||
.chain(args.into_iter());
|
||||
GenTok::S(Paren::Round, call.collect())
|
||||
},
|
||||
};
|
||||
Ok((tail4, tok.at(range)))
|
||||
},
|
||||
Err(e) => Ok((tail2, GenTok::Bottom(e).at(ctx.pos_lt(1, tail2)))),
|
||||
};
|
||||
async fn mac_tree<'a>(tail: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, MacTree)> {
|
||||
async fn mac_tree<'a>(
|
||||
tail: &'a str,
|
||||
args: &mut Vec<GenTokTree>,
|
||||
ctx: &'a LexContext<'a>,
|
||||
) -> OrcRes<(&'a str, MacTree)> {
|
||||
for (lp, rp, paren) in PARENS {
|
||||
let Some(mut body_tail) = tail.strip_prefix(*lp) else { continue };
|
||||
let mut items = Vec::new();
|
||||
return loop {
|
||||
let tail2 = body_tail.trim();
|
||||
let tail2 = body_tail.trim_start();
|
||||
if let Some(tail3) = tail2.strip_prefix(*rp) {
|
||||
break Ok((tail3, MacTree {
|
||||
pos: ctx.pos_tt(tail, tail3).pos(),
|
||||
tok: Rc::new(MacTok::S(*paren, items)),
|
||||
}));
|
||||
break Ok((tail3, MacTok::S(*paren, items).at(ctx.pos_tt(tail, tail3).pos())));
|
||||
} else if tail2.is_empty() {
|
||||
return Err(mk_errv(
|
||||
ctx.i().i("Unclosed block").await,
|
||||
@@ -40,22 +61,36 @@ impl Lexer for MacTreeLexer {
|
||||
[ctx.pos_lt(1, tail)],
|
||||
));
|
||||
}
|
||||
let (new_tail, new_item) = mac_tree(tail2, ctx).boxed_local().await?;
|
||||
let (new_tail, new_item) = mac_tree(tail2, args, ctx).boxed_local().await?;
|
||||
body_tail = new_tail;
|
||||
items.push(new_item);
|
||||
};
|
||||
}
|
||||
const INTERPOL: &[&str] = &["$", "..$"];
|
||||
for pref in INTERPOL {
|
||||
let Some(code) = tail.strip_prefix(pref) else { continue };
|
||||
todo!("Register parameter, and push this onto the argument stack held in the atom")
|
||||
if let Some(tail2) = tail.strip_prefix("$") {
|
||||
let (tail3, sub) = ctx.recurse(tail2).await?;
|
||||
let sr = ctx.pos_tt(tail, tail3);
|
||||
args.push(p_tree2gen(sub));
|
||||
return Ok((tail3, MacTok::Slot.at(sr.pos())));
|
||||
}
|
||||
if let Some(tail2) = tail.strip_prefix("\\") {
|
||||
let tail2 = tail2.trim_start();
|
||||
let (mut tail3, param) = mac_tree(tail2, args, ctx).boxed_local().await?;
|
||||
let mut body = Vec::new();
|
||||
loop {
|
||||
let tail4 = tail3.trim_start();
|
||||
if tail4.is_empty() || tail4.starts_with(|c| ")]}".contains(c)) {
|
||||
break;
|
||||
};
|
||||
let (tail5, body_tok) = mac_tree(tail4, args, ctx).boxed_local().await?;
|
||||
body.push(body_tok);
|
||||
tail3 = tail5;
|
||||
}
|
||||
Ok((tail3, MacTok::Lambda(param, body).at(ctx.pos_tt(tail, tail3).pos())))
|
||||
} else {
|
||||
let (tail2, sub) = ctx.recurse(tail).await?;
|
||||
let parsed = parse_tok(&sub, ctx).await.expect("Unexpected invalid token");
|
||||
Ok((tail2, parsed))
|
||||
}
|
||||
todo!("recursive lexer call");
|
||||
return Err(mk_errv(
|
||||
ctx.i().i("Expected token after '").await,
|
||||
format!("Expected a token after ', found {tail:?}"),
|
||||
[ctx.pos_lt(1, tail)],
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
mod instantiate_tpl;
|
||||
mod let_line;
|
||||
mod macro_lib;
|
||||
mod macro_system;
|
||||
mod macro_line;
|
||||
pub mod macro_system;
|
||||
pub mod mactree;
|
||||
mod mactree_lexer;
|
||||
pub mod recur_state;
|
||||
mod resolve;
|
||||
mod rule;
|
||||
|
||||
use mactree::{MacTok, MacTree};
|
||||
|
||||
59
orchid-std/src/macros/recur_state.rs
Normal file
59
orchid-std/src/macros/recur_state.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
use never::Never;
|
||||
use orchid_base::interner::Tok;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_extension::atom::Atomic;
|
||||
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RulePath {
|
||||
pub module: Sym,
|
||||
pub main_kw: Tok<String>,
|
||||
pub rule: u32,
|
||||
}
|
||||
impl RulePath {
|
||||
pub fn name(&self) -> String { format!("rule::{}::{}", self.main_kw, self.rule) }
|
||||
}
|
||||
impl fmt::Display for RulePath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Rule {}::({})::{}", self.module, self.main_kw, self.rule)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum RecurState {
|
||||
Bottom,
|
||||
Recursive { path: RulePath, prev: Rc<RecurState> },
|
||||
}
|
||||
impl RecurState {
|
||||
pub fn base(path: RulePath) -> Self {
|
||||
RecurState::Recursive { path, prev: Rc::new(RecurState::Bottom) }
|
||||
}
|
||||
pub fn push(&self, new: RulePath) -> Option<Self> {
|
||||
let mut cur = self;
|
||||
while let Self::Recursive { path, prev } = cur {
|
||||
if &new == path {
|
||||
return None;
|
||||
}
|
||||
cur = prev;
|
||||
}
|
||||
Some(Self::Recursive { path: new, prev: Rc::new(self.clone()) })
|
||||
}
|
||||
}
|
||||
impl Atomic for RecurState {
|
||||
type Data = Option<()>;
|
||||
type Variant = OwnedVariant;
|
||||
}
|
||||
impl OwnedAtom for RecurState {
|
||||
type Refs = Never;
|
||||
|
||||
async fn val(&self) -> Cow<'_, Self::Data> {
|
||||
Cow::Owned(match self {
|
||||
Self::Bottom => None,
|
||||
Self::Recursive { .. } => Some(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
107
orchid-std/src/macros/resolve.rs
Normal file
107
orchid-std/src/macros/resolve.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use futures::FutureExt;
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use orchid_base::error::mk_errv;
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::sym;
|
||||
use orchid_base::tree::Paren;
|
||||
use orchid_extension::conv::ToExpr;
|
||||
use orchid_extension::coroutine_exec::ExecHandle;
|
||||
use orchid_extension::gen_expr::{GExpr, bot, call, sym_ref};
|
||||
use orchid_extension::system::SysCtx;
|
||||
|
||||
use crate::macros::macro_line::{Macro, Rule};
|
||||
use crate::macros::recur_state::{RecurState, RulePath};
|
||||
use crate::macros::rule::matcher::{NamedMatcher, PriodMatcher};
|
||||
use crate::macros::rule::state::{MatchState, StateEntry};
|
||||
use crate::{MacTok, MacTree};
|
||||
|
||||
pub struct ResolveCtx<'a> {
|
||||
pub ctx: SysCtx,
|
||||
pub recur: RecurState,
|
||||
pub h: ExecHandle<'a>,
|
||||
pub named: HashMap<Sym, Vec<(&'a NamedMatcher, &'a Macro, &'a Rule)>>,
|
||||
pub priod: Vec<(&'a PriodMatcher, &'a Macro, &'a Rule)>,
|
||||
}
|
||||
|
||||
pub async fn resolve(ctx: &mut ResolveCtx<'_>, value: &MacTree) -> Option<MacTree> {
|
||||
match value.tok() {
|
||||
MacTok::Ph(_) | MacTok::Slot => panic!("Forbidden element in value mactree"),
|
||||
MacTok::Bottom(_) | MacTok::Value(_) | MacTok::Name(_) => None,
|
||||
MacTok::Lambda(arg, body) =>
|
||||
Some(MacTok::Lambda(arg.clone(), resolve_seq(ctx, body).await?).at(value.pos())),
|
||||
MacTok::S(ptyp, body) => Some(MacTok::S(*ptyp, resolve_seq(ctx, body).await?).at(value.pos())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn resolve_seq(ctx: &mut ResolveCtx<'_>, val: &[MacTree]) -> Option<Vec<MacTree>> {
|
||||
let mut any_changed = false;
|
||||
let mut i = 0;
|
||||
let mut val = val.to_vec();
|
||||
while i < val.len() {
|
||||
let MacTok::Name(key) = val[i].tok() else { continue };
|
||||
let Some(options) = ctx.named.get(key) else { continue };
|
||||
let matches = (options.iter())
|
||||
.filter_map(|r| Some((r.1, r.2, r.0.apply(&val[i..], |_| false)?)))
|
||||
.collect_vec();
|
||||
match matches.len() {
|
||||
0 => i += 1,
|
||||
1 => {
|
||||
any_changed = true;
|
||||
let (mac, rule, (state, tail)) = matches.into_iter().exactly_one().unwrap();
|
||||
let end = val.len() - tail.len();
|
||||
let new_i = val.len() - tail.len();
|
||||
let body_call = mk_body_call(mac, rule, &state, &ctx.ctx, ctx.recur.clone()).await;
|
||||
std::mem::drop(state);
|
||||
val.splice(i..end, [MacTok::Value(ctx.h.register(body_call).await).at(Pos::None)]);
|
||||
i = new_i;
|
||||
},
|
||||
2.. => todo!("Named macros conflict!"),
|
||||
}
|
||||
}
|
||||
for (matcher, mac, rule) in &ctx.priod {
|
||||
let Some(state) = matcher.apply(&val, |_| false) else { continue };
|
||||
return Some(vec![
|
||||
MacTok::Value(
|
||||
ctx.h.register(mk_body_call(mac, rule, &state, &ctx.ctx, ctx.recur.clone()).await).await,
|
||||
)
|
||||
.at(Pos::None),
|
||||
]);
|
||||
}
|
||||
for expr in val.iter_mut() {
|
||||
if let Some(new) = resolve(ctx, expr).boxed_local().await {
|
||||
*expr = new;
|
||||
any_changed = true;
|
||||
}
|
||||
}
|
||||
if any_changed { Some(val) } else { None }
|
||||
}
|
||||
|
||||
async fn mk_body_call(
|
||||
mac: &Macro,
|
||||
rule: &Rule,
|
||||
state: &MatchState<'_>,
|
||||
ctx: &SysCtx,
|
||||
recur: RecurState,
|
||||
) -> GExpr {
|
||||
let rule_path =
|
||||
RulePath { module: mac.0.module.clone(), main_kw: mac.0.own_kws[0].clone(), rule: rule.index };
|
||||
let Some(new_recur) = recur.push(rule_path.clone()) else {
|
||||
return bot(mk_errv(
|
||||
ctx.i().i("Circular macro dependency").await,
|
||||
format!("The definition of {rule_path} is circular"),
|
||||
[rule.pos.clone()],
|
||||
));
|
||||
};
|
||||
let mut call_args = vec![sym_ref(mac.0.module.suffix([rule.body_name.clone()], ctx.i()).await)];
|
||||
for name in rule.placeholders.iter() {
|
||||
call_args.push(match state.get(name).expect("Missing state entry for placeholder") {
|
||||
StateEntry::Scalar(scal) => (**scal).clone().to_expr().await,
|
||||
StateEntry::Vec(vec) => MacTok::S(Paren::Round, vec.to_vec()).at(Pos::None).to_expr().await,
|
||||
});
|
||||
}
|
||||
call_args
|
||||
.push(call([sym_ref(sym!(macros::resolve_recur; ctx.i()).await), new_recur.to_expr().await]));
|
||||
call(call_args)
|
||||
}
|
||||
@@ -16,7 +16,7 @@ pub type MaxVecSplit<'a> = (&'a [MacTree], (Tok<String>, u8, bool), &'a [MacTree
|
||||
/// Derive the details of the central vectorial and the two sides from a
|
||||
/// slice of Expr's
|
||||
#[must_use]
|
||||
fn split_at_max_vec(pattern: &[MacTree]) -> Option<MaxVecSplit> {
|
||||
fn split_at_max_vec(pattern: &'_ [MacTree]) -> Option<MaxVecSplit<'_>> {
|
||||
let rngidx = pattern
|
||||
.iter()
|
||||
.position_max_by_key(|expr| vec_attrs(expr).map(|attrs| attrs.1 as i64).unwrap_or(-1))?;
|
||||
@@ -31,7 +31,6 @@ fn scal_cnt<'a>(iter: impl Iterator<Item = &'a MacTree>) -> usize {
|
||||
iter.take_while(|expr| vec_attrs(expr).is_none()).count()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub async fn mk_any(pattern: &[MacTree], i: &Interner) -> OrcRes<AnyMatcher> {
|
||||
let left_split = scal_cnt(pattern.iter());
|
||||
if pattern.len() <= left_split {
|
||||
@@ -49,13 +48,11 @@ pub async fn mk_any(pattern: &[MacTree], i: &Interner) -> OrcRes<AnyMatcher> {
|
||||
}
|
||||
|
||||
/// Pattern MUST NOT contain vectorial placeholders
|
||||
#[must_use]
|
||||
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 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");
|
||||
@@ -116,7 +113,6 @@ pub async fn mk_vec(pattern: &[MacTree], i: &Interner) -> OrcRes<VecMatcher> {
|
||||
}
|
||||
|
||||
/// Pattern MUST NOT be a vectorial placeholder
|
||||
#[must_use]
|
||||
async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes<ScalMatcher> {
|
||||
Ok(match &*pattern.tok {
|
||||
MacTok::Name(n) => ScalMatcher::Name(n.clone()),
|
||||
@@ -140,8 +136,6 @@ async fn mk_scalar(pattern: &MacTree, i: &Interner) -> OrcRes<ScalMatcher> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::rc::Rc;
|
||||
|
||||
use orchid_base::interner::Interner;
|
||||
use orchid_base::location::SrcRange;
|
||||
use orchid_base::sym;
|
||||
@@ -149,15 +143,14 @@ mod test {
|
||||
use test_executors::spin_on;
|
||||
|
||||
use super::mk_any;
|
||||
use crate::macros::MacTok;
|
||||
use crate::macros::mactree::{Ph, PhKind};
|
||||
use crate::macros::{MacTok, MacTree};
|
||||
|
||||
#[test]
|
||||
fn test_scan() {
|
||||
spin_on(async {
|
||||
let i = Interner::new_master();
|
||||
let ex =
|
||||
|tok: MacTok| async { MacTree { tok: Rc::new(tok), pos: SrcRange::mock(&i).await.pos() } };
|
||||
let ex = |tok: MacTok| async { tok.at(SrcRange::mock(&i).await.pos()) };
|
||||
let pattern = vec![
|
||||
ex(MacTok::Ph(Ph {
|
||||
kind: PhKind::Vector { priority: 0, at_least_one: false },
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
use itertools::Itertools;
|
||||
use orchid_base::error::OrcRes;
|
||||
use orchid_base::interner::Interner;
|
||||
use orchid_base::interner::{Interner, Tok};
|
||||
use orchid_base::location::Pos;
|
||||
use orchid_base::name::Sym;
|
||||
|
||||
@@ -16,46 +15,47 @@ use super::vec_match::vec_match;
|
||||
use crate::macros::mactree::{Ph, PhKind};
|
||||
use crate::macros::{MacTok, MacTree};
|
||||
|
||||
pub fn first_is_vec(pattern: &[MacTree]) -> bool { vec_attrs(pattern.first().unwrap()).is_some() }
|
||||
pub fn last_is_vec(pattern: &[MacTree]) -> bool { vec_attrs(pattern.last().unwrap()).is_some() }
|
||||
|
||||
pub struct NamedMatcher(AnyMatcher);
|
||||
pub struct NamedMatcher {
|
||||
inner: AnyMatcher,
|
||||
head: Sym,
|
||||
after_tok: Tok<String>,
|
||||
}
|
||||
impl NamedMatcher {
|
||||
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"
|
||||
);
|
||||
|
||||
Ok(Self(match last_is_vec(pattern) {
|
||||
true => mk_any(pattern, i).await,
|
||||
let head = match pattern.first().map(|tree| tree.tok()) {
|
||||
Some(MacTok::Name(name)) => name.clone(),
|
||||
_ => panic!("Named matchers must begin with a name"),
|
||||
};
|
||||
let after_tok = i.i("::after").await;
|
||||
let inner = match pattern.last().and_then(vec_attrs).is_some() {
|
||||
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) }];
|
||||
mk_any(&pattern.iter().chain(&suffix).cloned().collect_vec(), i).await
|
||||
let suffix = [MacTok::Ph(Ph { name: after_tok.clone(), kind }).at(Pos::None)];
|
||||
mk_any(&pattern.iter().cloned().chain(suffix).collect_vec(), i).await?
|
||||
},
|
||||
}?))
|
||||
};
|
||||
Ok(Self { after_tok, inner, head })
|
||||
}
|
||||
pub fn head(&self) -> Sym { self.head.clone() }
|
||||
/// Also returns the tail, if any, which should be matched further
|
||||
/// Note that due to how priod works below, the main usable information from
|
||||
/// the tail is its length
|
||||
pub async fn apply<'a>(
|
||||
pub fn apply<'a>(
|
||||
&self,
|
||||
seq: &'a [MacTree],
|
||||
i: &Interner,
|
||||
save_loc: impl Fn(Sym) -> bool,
|
||||
) -> Option<(MatchState<'a>, &'a [MacTree])> {
|
||||
let mut state = any_match(&self.0, seq, &save_loc)?;
|
||||
match state.remove(i.i("::after").await) {
|
||||
Some(StateEntry::Scalar(_)) => panic!("::after can never be a scalar entry!"),
|
||||
let mut state = any_match(&self.inner, seq, &save_loc)?;
|
||||
match state.remove(self.after_tok.clone()) {
|
||||
Some(StateEntry::Scalar(_)) => panic!("{} can never be a scalar entry!", self.after_tok),
|
||||
Some(StateEntry::Vec(v)) => Some((state, v)),
|
||||
None => Some((state, &[][..])),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for NamedMatcher {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) }
|
||||
}
|
||||
impl fmt::Debug for NamedMatcher {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "NamedMatcher({self})") }
|
||||
|
||||
@@ -54,6 +54,7 @@ impl<'a> MatchState<'a> {
|
||||
pub fn from_name(name: Sym, location: Pos) -> Self {
|
||||
Self { name_posv: HashMap::from([(name, vec![location])]), placeholders: HashMap::new() }
|
||||
}
|
||||
pub fn get(&self, key: &Tok<String>) -> Option<&StateEntry<'a>> { self.placeholders.get(key) }
|
||||
pub fn remove(&mut self, name: Tok<String>) -> Option<StateEntry<'a>> {
|
||||
self.placeholders.remove(&name)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use orchid_extension::entrypoint::ExtensionData;
|
||||
use orchid_extension::tokio::tokio_main;
|
||||
use orchid_std::StdSystem;
|
||||
use orchid_std::{MacroSystem, StdSystem};
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn main() { tokio_main(ExtensionData::new("orchid-std::main", &[&StdSystem])).await }
|
||||
pub async fn main() {
|
||||
tokio_main(ExtensionData::new("orchid-std::main", &[&StdSystem, &MacroSystem])).await
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use orchid_base::error::OrcRes;
|
||||
use orchid_base::number::{num_to_err, parse_num};
|
||||
use orchid_base::number::{num_to_errv, parse_num};
|
||||
use orchid_extension::atom::ToAtom;
|
||||
use orchid_extension::lexer::{LexContext, Lexer};
|
||||
use orchid_extension::tree::{GenTokTree, x_tok};
|
||||
@@ -17,8 +17,8 @@ impl Lexer for NumLexer {
|
||||
let (chars, tail) = all.split_at(ends_at.unwrap_or(all.len()));
|
||||
let fac = match parse_num(chars) {
|
||||
Ok(numeric) => Num(numeric).to_atom_factory(),
|
||||
Err(e) => return Err(num_to_err(e, ctx.pos(all), &ctx.src, ctx.ctx.i()).await.into()),
|
||||
Err(e) => return Err(num_to_errv(e, ctx.pos(all), ctx.src(), ctx.ctx.i()).await),
|
||||
};
|
||||
Ok((tail, x_tok(fac).at(ctx.pos_lt(chars.len(), tail))))
|
||||
Ok((tail, x_tok(fac).await.at(ctx.pos_lt(chars.len(), tail))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ impl OwnedAtom for StrAtom {
|
||||
async fn serialize(&self, _: SysCtx, sink: Pin<&mut (impl Write + ?Sized)>) -> Self::Refs {
|
||||
self.deref().encode(sink).await
|
||||
}
|
||||
async fn print<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||
async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||
format!("{:?}", &*self.0).into()
|
||||
}
|
||||
async fn deserialize(mut ctx: impl DeserializeCtx, _: Self::Refs) -> Self {
|
||||
@@ -69,7 +69,7 @@ impl From<Tok<String>> for IntStrAtom {
|
||||
impl OwnedAtom for IntStrAtom {
|
||||
type Refs = ();
|
||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.0.to_api()) }
|
||||
async fn print<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||
async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||
format!("{:?}i", *self.0).into()
|
||||
}
|
||||
async fn serialize(&self, _: SysCtx, write: Pin<&mut (impl Write + ?Sized)>) {
|
||||
@@ -108,7 +108,7 @@ impl TryFromExpr for OrcString {
|
||||
}
|
||||
let ctx = expr.ctx();
|
||||
match TypAtom::<IntStrAtom>::try_from_expr(expr).await {
|
||||
Ok(t) => Ok(OrcString { ctx: t.data.ctx(), kind: OrcStringKind::Int(t) }),
|
||||
Ok(t) => Ok(OrcString { ctx: t.untyped.ctx().clone(), kind: OrcStringKind::Int(t) }),
|
||||
Err(e) => Err(mk_errv(ctx.i().i("A string was expected").await, "", e.pos_iter())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use itertools::Itertools;
|
||||
use orchid_base::error::{OrcErr, OrcRes, mk_err, mk_errv};
|
||||
use orchid_base::error::{OrcErr, OrcErrv, OrcRes, mk_errv};
|
||||
use orchid_base::interner::Interner;
|
||||
use orchid_base::location::SrcRange;
|
||||
use orchid_base::name::Sym;
|
||||
use orchid_base::parse::ParseCtx;
|
||||
use orchid_base::sym;
|
||||
use orchid_base::tree::wrap_tokv;
|
||||
use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable};
|
||||
use orchid_extension::parser::p_tree2gen;
|
||||
use orchid_extension::tree::{GenTokTree, ref_tok, x_tok};
|
||||
|
||||
use super::str_atom::IntStrAtom;
|
||||
@@ -32,16 +34,16 @@ struct StringError {
|
||||
|
||||
impl StringError {
|
||||
/// Convert into project error for reporting
|
||||
pub async fn into_proj(self, path: &Sym, pos: u32, i: &Interner) -> OrcErr {
|
||||
pub async fn into_proj(self, path: &Sym, pos: u32, i: &Interner) -> OrcErrv {
|
||||
let start = pos + self.pos;
|
||||
mk_err(
|
||||
mk_errv(
|
||||
i.i("Failed to parse string").await,
|
||||
match self.kind {
|
||||
StringErrorKind::NotHex => "Expected a hex digit",
|
||||
StringErrorKind::BadCodePoint => "The specified number is not a Unicode code point",
|
||||
StringErrorKind::BadEscSeq => "Unrecognized escape sequence",
|
||||
},
|
||||
[SrcRange::new(start..start + 1, path).pos().into()],
|
||||
[SrcRange::new(start..start + 1, path).pos()],
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -97,7 +99,7 @@ impl Lexer for StringLexer {
|
||||
const CHAR_FILTER: &'static [std::ops::RangeInclusive<char>] = &['"'..='"', '`'..='`'];
|
||||
async fn lex<'a>(all: &'a str, ctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> {
|
||||
let Some(mut tail) = all.strip_prefix('"') else {
|
||||
return Err(err_not_applicable(ctx.ctx.i()).await.into());
|
||||
return Err(err_not_applicable(ctx.ctx.i()).await);
|
||||
};
|
||||
let mut ret = None;
|
||||
let mut cur = String::new();
|
||||
@@ -110,15 +112,17 @@ impl Lexer for StringLexer {
|
||||
) -> GenTokTree {
|
||||
let str_val_res = parse_string(&str.split_off(0));
|
||||
if let Err(e) = &str_val_res {
|
||||
err.push(e.clone().into_proj(&ctx.src, ctx.pos(tail) - str.len() as u32, ctx.i()).await);
|
||||
err.extend(e.clone().into_proj(ctx.src(), ctx.pos(tail) - str.len() as u32, ctx.i()).await);
|
||||
}
|
||||
let str_val = str_val_res.unwrap_or_default();
|
||||
x_tok(IntStrAtom::from(ctx.i().i(&*str_val).await)).at(ctx.pos_lt(str.len() as u32, tail))
|
||||
as GenTokTree
|
||||
x_tok(IntStrAtom::from(ctx.i().i(&*str_val).await))
|
||||
.await
|
||||
.at(ctx.pos_lt(str.len() as u32, tail)) as GenTokTree
|
||||
}
|
||||
let add_frag = |prev: Option<GenTokTree>, new: GenTokTree| async {
|
||||
let Some(prev) = prev else { return new };
|
||||
let concat_fn = ref_tok(sym!(std::string::concat; ctx.i()).await)
|
||||
.await
|
||||
.at(SrcRange::zw(prev.sr.path(), prev.sr.start()));
|
||||
wrap_tokv([concat_fn, prev, new])
|
||||
};
|
||||
@@ -129,7 +133,7 @@ impl Lexer for StringLexer {
|
||||
ret = Some(add_frag(ret, str_to_gen(&mut cur, tail, &mut errors, ctx).await).await);
|
||||
let (new_tail, tree) = ctx.recurse(rest).await?;
|
||||
tail = new_tail;
|
||||
ret = Some(add_frag(ret, tree).await);
|
||||
ret = Some(add_frag(ret, p_tree2gen(tree)).await);
|
||||
} else if tail.starts_with('\\') {
|
||||
// parse_string will deal with it, we just have to skip the next char
|
||||
tail = &tail[2..];
|
||||
@@ -143,7 +147,7 @@ impl Lexer for StringLexer {
|
||||
return Err(mk_errv(
|
||||
ctx.i().i("No string end").await,
|
||||
"String never terminated with \"",
|
||||
[SrcRange::new(range.clone(), &ctx.src)],
|
||||
[SrcRange::new(range.clone(), ctx.src())],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user