Gitbutler >:(

I don't understand this piece of software at all
This commit is contained in:
2024-12-31 00:03:39 +01:00
parent 3a3ae98aff
commit e780969c6c
42 changed files with 1119 additions and 498 deletions

View File

@@ -6,10 +6,10 @@ use std::sync::{Arc, RwLock};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use orchid_base::error::OrcErrv;
use orchid_base::interner::deintern;
use orchid_base::location::Pos;
use orchid_base::match_mapping;
use orchid_base::name::Sym;
use orchid_base::tree::AtomTok;
use orchid_base::tree::AtomRepr;
use crate::api;
use crate::extension::AtomHand;
@@ -41,12 +41,12 @@ impl Expr {
pub fn resolve(tk: api::ExprTicket) -> Option<Self> {
KNOWN_EXPRS.read().unwrap().get(&tk).cloned()
}
pub fn from_api(api: api::Expression, ctx: &mut ExprParseCtx) -> Self {
pub fn from_api(api: &api::Expression, ctx: &mut ExprParseCtx) -> Self {
if let api::ExpressionKind::Slot(tk) = &api.kind {
return Self::resolve(*tk).expect("Invalid slot");
}
Self {
kind: Arc::new(RwLock::new(ExprKind::from_api(api.kind, ctx))),
kind: Arc::new(RwLock::new(ExprKind::from_api(&api.kind, ctx))),
is_canonical: Arc::default(),
pos: Pos::from_api(&api.location),
}
@@ -81,24 +81,24 @@ pub enum ExprKind {
Seq(Expr, Expr),
Call(Expr, Expr),
Atom(AtomHand),
Argument,
Arg,
Lambda(Option<PathSet>, Expr),
Bottom(OrcErrv),
Const(Sym),
}
impl ExprKind {
pub fn from_api(api: api::ExpressionKind, ctx: &mut ExprParseCtx) -> Self {
use api::ExpressionKind as K;
match api {
K::Slot(_) => panic!("Handled in Expr"),
K::Lambda(id, b) => ExprKind::Lambda(PathSet::from_api(id, &b), Expr::from_api(*b, ctx)),
K::Arg(_) => ExprKind::Argument,
K::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(&b)),
K::Call(f, x) => ExprKind::Call(Expr::from_api(*f, ctx), Expr::from_api(*x, ctx)),
K::Const(c) => ExprKind::Const(Sym::from_tok(deintern(c)).unwrap()),
K::NewAtom(a) => ExprKind::Atom(AtomHand::from_api(a)),
K::Seq(a, b) => ExprKind::Seq(Expr::from_api(*a, ctx), Expr::from_api(*b, ctx)),
}
pub fn from_api(api: &api::ExpressionKind, ctx: &mut ExprParseCtx) -> Self {
match_mapping!(api, api::ExpressionKind => ExprKind {
Lambda(id => PathSet::from_api(*id, api), b => Expr::from_api(b, ctx)),
Bottom(b => OrcErrv::from_api(b)),
Call(f => Expr::from_api(f, ctx), x => Expr::from_api(x, ctx)),
Const(c => Sym::from_api(*c)),
Seq(a => Expr::from_api(a, ctx), b => Expr::from_api(b, ctx)),
} {
api::ExpressionKind::Arg(_) => ExprKind::Arg,
api::ExpressionKind::NewAtom(a) => ExprKind::Atom(AtomHand::from_api(a.clone())),
api::ExpressionKind::Slot(_) => panic!("Handled in Expr"),
})
}
}
@@ -121,19 +121,20 @@ impl PathSet {
self.steps.push_front(step);
self
}
pub fn from_api(id: u64, b: &api::Expression) -> Option<Self> {
pub fn from_api(id: u64, api: &api::ExpressionKind) -> Option<Self> {
use api::ExpressionKind as K;
match &b.kind {
match &api {
K::Arg(id2) => (id == *id2).then(|| Self { steps: VecDeque::new(), next: None }),
K::Bottom(_) | K::Const(_) | K::NewAtom(_) | K::Slot(_) => None,
K::Lambda(_, b) => Self::from_api(id, b),
K::Call(l, r) | K::Seq(l, r) => match (Self::from_api(id, l), Self::from_api(id, r)) {
(Some(a), Some(b)) =>
Some(Self { steps: VecDeque::new(), next: Some((Box::new(a), Box::new(b))) }),
(Some(l), None) => Some(l.after(Step::Left)),
(None, Some(r)) => Some(r.after(Step::Right)),
(None, None) => None,
},
K::Lambda(_, b) => Self::from_api(id, &b.kind),
K::Call(l, r) | K::Seq(l, r) =>
match (Self::from_api(id, &l.kind), Self::from_api(id, &r.kind)) {
(Some(a), Some(b)) =>
Some(Self { steps: VecDeque::new(), next: Some((Box::new(a), Box::new(b))) }),
(Some(l), None) => Some(l.after(Step::Left)),
(None, Some(r)) => Some(r.after(Step::Right)),
(None, None) => None,
},
}
}
}

View File

@@ -1,6 +1,6 @@
use std::collections::VecDeque;
use std::num::NonZero;
use std::ops::{Deref, Range};
use std::ops::Deref;
use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU64, Ordering};
use std::sync::mpsc::{sync_channel, SyncSender};
use std::sync::{Arc, Mutex, OnceLock, RwLock, Weak};
@@ -11,23 +11,23 @@ use hashbrown::hash_map::Entry;
use hashbrown::HashMap;
use itertools::Itertools;
use lazy_static::lazy_static;
use orchid_api::TStrv;
use orchid_api_traits::Request;
use orchid_base::char_filter::char_filter_match;
use orchid_base::error::{OrcErrv, OrcRes};
use orchid_base::interner::{deintern, intern, Tok};
use orchid_base::interner::{intern, Tok};
use orchid_base::location::Pos;
use orchid_base::logging::Logger;
use orchid_base::macros::{mtreev_from_api, mtreev_to_api};
use orchid_base::macros::mtreev_from_api;
use orchid_base::parse::Comment;
use orchid_base::reqnot::{ReqNot, Requester as _};
use orchid_base::tree::{ttv_from_api, AtomTok};
use orchid_base::tree::{ttv_from_api, AtomRepr};
use orchid_base::clone;
use ordered_float::NotNan;
use substack::{Stackframe, Substack};
use crate::api;
use crate::expr::Expr;
use crate::macros::macro_recur;
use crate::macros::{macro_recur, macro_treev_to_api};
use crate::tree::{Member, ParsTokTree};
#[derive(Debug, destructure)]
@@ -56,11 +56,11 @@ impl Drop for AtomData {
#[derive(Clone, Debug)]
pub struct AtomHand(Arc<AtomData>);
impl AtomHand {
fn create_new(api::Atom { data, drop, owner }: api::Atom) -> Self {
let owner = System::resolve(owner).expect("Atom owned by non-existing system");
Self(Arc::new(AtomData { data, drop, owner }))
}
pub fn from_api(atom: api::Atom) -> Self {
fn create_new(api::Atom { data, drop, owner }: api::Atom) -> AtomHand {
let owner = System::resolve(owner).expect("Atom owned by non-existing system");
AtomHand(Arc::new(AtomData { data, drop, owner }))
}
if let Some(id) = atom.drop {
lazy_static! {
static ref OWNED_ATOMS: Mutex<HashMap<(api::SysId, api::AtomId), Weak<AtomData>>> =
@@ -73,11 +73,11 @@ impl AtomHand {
return Self(atom);
}
}
let new = Self::create_new(atom);
let new = create_new(atom);
owned_g.insert((owner, id), Arc::downgrade(&new.0));
new
} else {
Self::create_new(atom)
create_new(atom)
}
}
pub fn call(self, arg: Expr) -> api::Expression {
@@ -89,15 +89,15 @@ impl AtomHand {
Err(hand) => reqnot.request(api::CallRef(hand.api_ref(), ticket)),
}
}
pub fn req(&self, key: TStrv, req: Vec<u8>) -> Option<Vec<u8>> {
pub fn req(&self, key: api::TStrv, req: Vec<u8>) -> Option<Vec<u8>> {
self.0.owner.reqnot().request(api::Fwded(self.0.api_ref(), key, req))
}
pub fn api_ref(&self) -> api::Atom { self.0.api_ref() }
pub fn print(&self) -> String { self.0.owner.reqnot().request(api::AtomPrint(self.0.api_ref())) }
}
impl AtomTok for AtomHand {
type Context = ();
fn from_api(atom: &orchid_api::Atom, _: Range<u32>, (): &mut Self::Context) -> Self {
impl AtomRepr for AtomHand {
type Ctx = ();
fn from_api(atom: &orchid_api::Atom, _: Pos, (): &mut Self::Ctx) -> Self {
Self::from_api(atom.clone())
}
fn to_api(&self) -> orchid_api::Atom { self.api_ref() }
@@ -177,11 +177,15 @@ impl Extension {
|hand, req| match req {
api::ExtHostReq::Ping(ping) => hand.handle(&ping, &()),
api::ExtHostReq::IntReq(intreq) => match intreq {
api::IntReq::InternStr(s) => hand.handle(&s, &intern(&**s.0).marker()),
api::IntReq::InternStrv(v) => hand.handle(&v, &intern(&*v.0).marker()),
api::IntReq::ExternStr(si) => hand.handle(&si, &deintern(si.0).arc()),
api::IntReq::ExternStrv(vi) =>
hand.handle(&vi, &Arc::new(deintern(vi.0).iter().map(|t| t.marker()).collect_vec())),
api::IntReq::InternStr(s) => hand.handle(&s, &intern(&**s.0).to_api()),
api::IntReq::InternStrv(v) => hand.handle(&v, &intern(&*v.0).to_api()),
api::IntReq::ExternStr(si) => hand.handle(&si, &Tok::<String>::from_api(si.0).arc()),
api::IntReq::ExternStrv(vi) => hand.handle(&vi, &Arc::new(
Tok::<Vec<Tok<String>>>::from_api(vi.0)
.iter()
.map(|t| t.to_api())
.collect_vec()
)),
},
api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => {
let sys = System::resolve(atom.owner).unwrap();
@@ -207,7 +211,12 @@ impl Extension {
})
},
api::ExtHostReq::RunMacros(ref rm @ api::RunMacros{ ref run_id, ref query }) => {
hand.handle(rm, &macro_recur(*run_id, mtreev_from_api(query)).map(|x| mtreev_to_api(&x)))
hand.handle(rm,
&macro_recur(*run_id,
mtreev_from_api(query, &mut |_| panic!("Recursion never contains atoms"))
)
.map(|x| macro_treev_to_api(*run_id, x))
)
}
},
),
@@ -255,13 +264,13 @@ impl SystemCtor {
exprs: RwLock::default(),
lex_filter: sys_inst.lex_filter,
const_root: OnceLock::new(),
line_types: sys_inst.line_types.into_iter().map(deintern).collect(),
line_types: sys_inst.line_types.into_iter().map(Tok::from_api).collect(),
id,
}));
let root = (sys_inst.const_root.into_iter())
.map(|(k, v)| Member::from_api(
api::Member { name: k, kind: v },
Substack::Bottom.push(deintern(k)),
Substack::Bottom.push(Tok::from_api(k)),
&data
))
.collect_vec();
@@ -342,7 +351,7 @@ impl System {
});
// Pass control to extension
let ret =
self.reqnot().request(api::LexExpr { id, pos, sys: self.id(), text: source.marker() });
self.reqnot().request(api::LexExpr { id, pos, sys: self.id(), text: source.to_api() });
// collect sender to unblock recursion handler thread before returning
LEX_RECUR.lock().unwrap().remove(&id);
ret.transpose()

View File

@@ -3,8 +3,8 @@ use std::sync::Arc;
use hashbrown::HashMap;
use orchid_base::error::{mk_errv, OrcErrv, OrcRes};
use orchid_base::intern;
use orchid_base::interner::{deintern, intern, Tok};
use orchid_base::{intern, match_mapping};
use orchid_base::interner::{intern, Tok};
use orchid_base::location::Pos;
use orchid_base::number::{num_to_err, parse_num};
use orchid_base::parse::{name_char, name_start, op_char, unrep_space};
@@ -122,7 +122,7 @@ pub fn lex_once(ctx: &mut LexCtx) -> OrcRes<ParsTokTree> {
body.push(lex_once(ctx)?);
ctx.trim_ws();
}
ParsTok::S(paren.clone(), body)
ParsTok::S(*paren, body)
} else if ctx.strip_prefix("macro") &&
!ctx.tail.chars().next().is_some_and(|x| x.is_ascii_alphabetic())
{
@@ -173,20 +173,19 @@ pub fn lex_once(ctx: &mut LexCtx) -> OrcRes<ParsTokTree> {
}
fn tt_to_owned(api: &api::TokenTree, ctx: &mut LexCtx<'_>) -> ParsTokTree {
let tok = match &api.token {
api::Token::Atom(atom) => ParsTok::Atom(AtomHand::from_api(atom.clone())),
api::Token::Bottom(err) => ParsTok::Bottom(OrcErrv::from_api(err)),
api::Token::LambdaHead(arg) => ParsTok::LambdaHead(ttv_to_owned(arg, ctx)),
api::Token::Lambda(arg, b) => ParsTok::Lambda(ttv_to_owned(arg, ctx), ttv_to_owned(b, ctx)),
api::Token::Name(name) => ParsTok::Name(deintern(*name)),
api::Token::S(p, b) => ParsTok::S(p.clone(), b.iter().map(|t| tt_to_owned(t, ctx)).collect()),
let tok = match_mapping!(&api.token, api::Token => ParsTok {
Atom(atom => AtomHand::from_api(atom.clone())),
Bottom(err => OrcErrv::from_api(err)),
LambdaHead(arg => ttv_to_owned(arg, ctx)),
Name(name => Tok::from_api(*name)),
S(p.clone(), b.iter().map(|t| tt_to_owned(t, ctx)).collect()),
BR, NS,
Comment(c.clone()),
Ph(ph => Ph::from_api(ph)),
Macro(*prio),
} {
api::Token::Slot(id) => return ctx.rm_subtree(*id),
api::Token::BR => ParsTok::BR,
api::Token::NS => ParsTok::NS,
api::Token::Comment(c) => ParsTok::Comment(c.clone()),
api::Token::Ph(ph) => ParsTok::Ph(Ph::from_api(ph)),
api::Token::Macro(prio) => ParsTok::Macro(*prio)
};
});
ParsTokTree { range: api.range.clone(), tok }
}

View File

@@ -8,3 +8,4 @@ pub mod parse;
pub mod subprocess;
pub mod tree;
pub mod macros;
pub mod rule;

View File

@@ -1,20 +1,85 @@
use std::sync::RwLock;
use crate::{api, rule::shared::Matcher, tree::Code};
use std::sync::{Arc, RwLock};
use hashbrown::HashMap;
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use lazy_static::lazy_static;
use orchid_base::macros::MTree;
use orchid_base::{macros::{mtreev_from_api, mtreev_to_api, MTok, MTree}, name::Sym};
use ordered_float::NotNan;
use trait_set::trait_set;
use crate::api::ParsId;
use crate::extension::AtomHand;
pub type MacTok = MTok<'static, AtomHand>;
pub type MacTree = MTree<'static, AtomHand>;
trait_set!{
trait MacroCB = Fn(Vec<MTree>) -> Option<Vec<MTree>> + Send + Sync;
trait MacroCB = Fn(Vec<MacTree>) -> Option<Vec<MacTree>> + Send + Sync;
}
lazy_static!{
static ref RECURSION: RwLock<HashMap<ParsId, Box<dyn MacroCB>>> = RwLock::default();
static ref RECURSION: RwLock<HashMap<api::ParsId, Box<dyn MacroCB>>> = RwLock::default();
static ref MACRO_SLOTS: RwLock<HashMap<api::ParsId,
HashMap<api::MacroTreeId, Arc<MacTok>>
>> = RwLock::default();
}
pub fn macro_recur(run_id: ParsId, input: Vec<MTree>) -> Option<Vec<MTree>> {
pub fn macro_recur(run_id: api::ParsId, input: Vec<MacTree>) -> Option<Vec<MacTree>> {
(RECURSION.read().unwrap()[&run_id])(input)
}
pub fn macro_treev_to_api(run_id: api::ParsId, mtree: Vec<MacTree>) -> Vec<api::MacroTree> {
let mut g = MACRO_SLOTS.write().unwrap();
let run_cache = g.get_mut(&run_id).expect("Parser run not found");
mtreev_to_api(&mtree, &mut |a: &AtomHand| {
let id = api::MacroTreeId((run_cache.len() as u64 + 1).try_into().unwrap());
run_cache.insert(id, Arc::new(MacTok::Atom(a.clone())));
api::MacroToken::Slot(id)
})
}
pub fn macro_treev_from_api(api: Vec<api::MacroTree>) -> Vec<MacTree> {
mtreev_from_api(&api, &mut |atom| MacTok::Atom(AtomHand::from_api(atom.clone())))
}
pub fn deslot_macro(run_id: api::ParsId, tree: &[MacTree]) -> Option<Vec<MacTree>> {
let mut slots = (MACRO_SLOTS.write().unwrap())
.remove(&run_id).expect("Run not found");
return work(&mut slots, tree);
fn work(
slots: &mut HashMap<api::MacroTreeId, Arc<MacTok>>,
tree: &[MacTree]
) -> Option<Vec<MacTree>> {
let items = (tree.iter())
.map(|t| Some(MacTree {
tok: match &*t.tok {
MacTok::Atom(_) | MacTok::Name(_) | MacTok::Ph(_) => return None,
MacTok::Ref(_) => panic!("Ref is an extension-local optimization"),
MacTok::Slot(slot) => slots.get(&slot.id()).expect("Slot not found").clone(),
MacTok::S(paren, b) => Arc::new(MacTok::S(*paren, work(slots, b)?)),
MacTok::Lambda(a, b) => Arc::new(match (work(slots, a), work(slots, b)) {
(None, None) => return None,
(Some(a), None) => MacTok::Lambda(a, b.clone()),
(None, Some(b)) => MacTok::Lambda(a.clone(), b),
(Some(a), Some(b)) => MacTok::Lambda(a, b),
}),
},
pos: t.pos.clone()
}))
.collect_vec();
let any_changed = items.iter().any(Option::is_some);
any_changed.then(|| {
(items.into_iter().enumerate())
.map(|(i, opt)| opt.unwrap_or_else(|| tree[i].clone()))
.collect_vec()
})
}
}
pub struct MacroRepo{
no_prio: Vec<(HashSet<Sym>, Matcher, Code)>,
prio: Vec<(HashSet<Sym>, NotNan<f64>, Matcher, Code)>,
}
pub fn match_on_exprv<'a>(target: &'a [MacTree], pattern: &[MacTree]) -> Option<MatchhState<'a>> {
}

View File

@@ -1,3 +1,4 @@
use std::sync::Arc;
use std::{iter, thread};
use itertools::Itertools;
@@ -16,6 +17,7 @@ use orchid_base::tree::{Paren, TokTree, Token};
use substack::Substack;
use crate::extension::{AtomHand, System};
use crate::macros::MacTree;
use crate::tree::{Code, CodeLocator, Item, ItemKind, Member, MemberKind, Module, ParsTokTree, Rule, RuleKind};
type ParsSnippet<'a> = Snippet<'a, 'static, AtomHand, Never>;
@@ -181,9 +183,7 @@ pub fn parse_const(tail: ParsSnippet) -> OrcRes<(Tok<String>, Vec<ParsTokTree>)>
Ok((name, tail.iter().flat_map(strip_fluff).collect_vec()))
}
pub fn parse_mtree<'a>(
mut snip: ParsSnippet<'a>
) -> OrcRes<Vec<MTree<'static>>> {
pub fn parse_mtree(mut snip: ParsSnippet<'_>) -> OrcRes<Vec<MacTree>> {
let mut mtreev = Vec::new();
while let Some((ttree, tail)) = snip.pop_front() {
let (range, tok, tail) = match &ttree.tok {
@@ -221,21 +221,14 @@ pub fn parse_mtree<'a>(
)),
Token::BR | Token::Comment(_) => continue,
Token::Bottom(e) => return Err(e.clone()),
Token::Lambda(arg, body) => {
let tok = MTok::Lambda(
parse_mtree(Snippet::new(&ttree, &arg))?,
parse_mtree(Snippet::new(&ttree, &body))?,
);
(ttree.range.clone(), tok, tail)
},
Token::LambdaHead(arg) => (
ttree.range.start..snip.pos().end,
MTok::Lambda(parse_mtree(Snippet::new(&ttree, &arg))?, parse_mtree(tail)?),
MTok::Lambda(parse_mtree(Snippet::new(ttree, arg))?, parse_mtree(tail)?),
Snippet::new(ttree, &[]),
),
Token::Slot(_) | Token::X(_) => panic!("Did not expect {} in parsed token tree", &ttree.tok),
};
mtreev.push(MTree { pos: Pos::Range(range.clone()), tok });
mtreev.push(MTree { pos: Pos::Range(range.clone()), tok: Arc::new(tok) });
snip = tail;
}
Ok(mtreev)
@@ -246,14 +239,14 @@ pub fn parse_macro(tail: ParsSnippet, macro_i: u16, path: Substack<Tok<String>>)
Parsed { tail, output: o@TokTree { tok: Token::S(Paren::Round, b), .. } } => (tail, o, b),
Parsed { output, .. } => return Err(mk_errv(
intern!(str: "m"),
format!("Macro blocks must either start with a block or a ..$:number"),
"Macro blocks must either start with a block or a ..$:number",
[Pos::Range(output.range.clone()).into()]
)),
};
expect_end(surplus)?;
let mut errors = Vec::new();
let mut rules = Vec::new();
for (i, item) in line_items(Snippet::new(prev, &block)).into_iter().enumerate() {
for (i, item) in line_items(Snippet::new(prev, block)).into_iter().enumerate() {
let Parsed { tail, output } = try_pop_no_fluff(item.tail)?;
if !output.is_kw(intern!(str: "rule")) {
errors.extend(mk_errv(

View File

@@ -0,0 +1,29 @@
use super::scal_match::scalv_match;
use super::shared::AnyMatcher;
use super::vec_match::vec_match;
use orchid_base::name::Sym;
use crate::macros::MacTree;
use crate::rule::state::MatchState;
#[must_use]
pub fn any_match<'a>(
matcher: &AnyMatcher,
seq: &'a [MacTree],
save_loc: &impl Fn(Sym) -> bool,
) -> Option<MatchState<'a>> {
match matcher {
AnyMatcher::Scalar(scalv) => scalv_match(scalv, seq, save_loc),
AnyMatcher::Vec { left, mid, right } => {
if seq.len() < left.len() + right.len() {
return None;
};
let left_split = left.len();
let right_split = seq.len() - right.len();
Some(
scalv_match(left, &seq[..left_split], save_loc)?
.combine(scalv_match(right, &seq[right_split..], save_loc)?)
.combine(vec_match(mid, &seq[left_split..right_split], save_loc)?),
)
},
}
}

View File

@@ -0,0 +1,151 @@
use orchid_api::PhKind;
use orchid_base::interner::Tok;
use itertools::Itertools;
use orchid_base::side::Side;
use orchid_base::tree::Ph;
use super::shared::{AnyMatcher, ScalMatcher, VecMatcher};
use crate::macros::{MacTok, MacTree};
use crate::rule::vec_attrs::vec_attrs;
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> {
let rngidx = pattern
.iter()
.position_max_by_key(|expr| vec_attrs(expr).map(|attrs| attrs.1 as i64).unwrap_or(-1))?;
let (left, not_left) = pattern.split_at(rngidx);
let (placeh, right) =
not_left.split_first().expect("The index of the greatest element must be less than the length");
vec_attrs(placeh).map(|attrs| (left, attrs, right))
}
#[must_use]
fn scal_cnt<'a>(iter: impl Iterator<Item = &'a MacTree>) -> usize {
iter.take_while(|expr| vec_attrs(expr).is_none()).count()
}
#[must_use]
pub fn mk_any(pattern: &[MacTree]) -> AnyMatcher {
let left_split = scal_cnt(pattern.iter());
if pattern.len() <= left_split {
return AnyMatcher::Scalar(mk_scalv(pattern));
}
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) }
}
/// Pattern MUST NOT contain vectorial placeholders
#[must_use]
fn mk_scalv(pattern: &[MacTree]) -> Vec<ScalMatcher> { pattern.iter().map(mk_scalar).collect() }
/// Pattern MUST start and end with a vectorial placeholder
#[must_use]
fn mk_vec(pattern: &[MacTree]) -> 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");
let (left, (key, _, nonzero), right) = split_at_max_vec(pattern)
.expect("pattern must have vectorial placeholders at least at either end");
let r_sep_size = scal_cnt(right.iter());
let (r_sep, r_side) = right.split_at(r_sep_size);
let l_sep_size = scal_cnt(left.iter().rev());
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)),
},
(_, &[]) => VecMatcher::Scan {
direction: Side::Right,
left: Box::new(mk_vec(l_side)),
sep: mk_scalv(l_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(),
}
},
}
}
/// Pattern MUST NOT be a vectorial placeholder
#[must_use]
fn mk_scalar(pattern: &MacTree) -> ScalMatcher {
match &*pattern.tok {
MacTok::Atom(_) => panic!("Atoms aren't supported in matchers"),
MacTok::Name(n) => ScalMatcher::Name(n.clone()),
MacTok::Ph(Ph { name, kind }) => match kind {
PhKind::Vector { .. } => {
panic!("Scalar matcher cannot be built from vector pattern")
},
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::Ref(_) | MacTok::Slot(_) => panic!("Extension-only variants"),
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use orchid_api::PhKind;
use orchid_base::{intern, location::SourceRange, sym, tree::Ph, tokens::Paren};
use crate::macros::{MacTok, MacTree};
use super::mk_any;
#[test]
fn test_scan() {
let ex = |tok: MacTok| MacTree{ tok: Arc::new(tok), pos: SourceRange::mock().pos() };
let pattern = vec![
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: intern!(str: "::prefix"),
})),
ex(MacTok::Name(sym!(prelude::do))),
ex(MacTok::S(
Paren::Round,
vec![
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: intern!(str: "expr"),
})),
ex(MacTok::Name(sym!(prelude::;))),
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 1, at_least_one: false },
name: intern!(str: "rest"),
})),
],
)),
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: intern!(str: "::suffix"),
})),
];
let matcher = mk_any(&pattern);
println!("{matcher}");
}
}

View File

@@ -0,0 +1,21 @@
//! Abstract definition of a rule matcher, so that the implementation can
//! eventually be swapped out for a different one.
use std::rc::Rc;
use orchid_base::name::Sym;
use super::state::State;
use crate::macros::MacTree;
/// Cacheable optimized structures for matching patterns on slices. This is
/// injected to allow experimentation in the matcher implementation.
pub trait Matcher {
/// Build matcher for a pattern
#[must_use]
fn new(pattern: Rc<Vec<MacTree>>) -> Self;
/// Apply matcher to a token sequence
#[must_use]
fn apply<'a>(&self, source: &'a [MacTree], save_loc: &impl Fn(Sym) -> bool)
-> Option<State<'a>>;
}

View File

@@ -0,0 +1,24 @@
//! Optimized form of macro pattern that can be quickly tested against the AST.
//!
//! # Construction
//!
//! convert pattern into hierarchy of plain, scan, middle
//! - plain: accept any sequence or any non-empty sequence
//! - scan: a single scalar pattern moves LTR or RTL, submatchers on either
//! side
//! - middle: two scalar patterns walk over all permutations of matches
//! while getting progressively closer to each other
//!
//! # Application
//!
//! walk over the current matcher's valid options and poll the submatchers
//! for each of them
mod any_match;
mod build;
mod scal_match;
pub mod shared;
mod vec_match;
pub mod state;
mod vec_attrs;
// pub mod matcher;

View File

@@ -0,0 +1,42 @@
use orchid_base::name::Sym;
use super::any_match::any_match;
use super::shared::ScalMatcher;
use crate::{macros::{MacTok, MacTree}, rule::state::{MatchState, StateEntry}};
#[must_use]
pub fn scal_match<'a>(
matcher: &ScalMatcher,
expr: &'a MacTree,
save_loc: &impl Fn(Sym) -> bool,
) -> Option<MatchState<'a>> {
match (matcher, &*expr.tok) {
(ScalMatcher::Name(n1), MacTok::Name(n2)) if n1 == n2 => Some(match save_loc(n1.clone()) {
true => MatchState::from_name(n1.clone(), expr.pos.clone()),
false => MatchState::default(),
}),
(ScalMatcher::Placeh { key }, _) =>
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,
}
}
#[must_use]
pub fn scalv_match<'a>(
matchers: &[ScalMatcher],
seq: &'a [MacTree],
save_loc: &impl Fn(Sym) -> bool,
) -> Option<MatchState<'a>> {
if seq.len() != matchers.len() {
return None;
}
let mut state = MatchState::default();
for (matcher, expr) in matchers.iter().zip(seq.iter()) {
state = state.combine(scal_match(matcher, expr, save_loc)?);
}
Some(state)
}

View File

@@ -0,0 +1,121 @@
//! Datastructures for cached pattern
use std::fmt;
use itertools::Itertools;
use orchid_base::interner::Tok;
use super::any_match::any_match;
use super::build::mk_any;
use orchid_base::name::Sym;
use crate::macros::MacTree;
use crate::rule::state::MatchState;
use orchid_base::side::Side;
use orchid_base::tokens::{Paren, PARENS};
pub enum ScalMatcher {
Name(Sym),
S(Paren, Box<AnyMatcher>),
Lambda(Box<AnyMatcher>, Box<AnyMatcher>),
Placeh { key: Tok<String> },
}
pub enum VecMatcher {
Placeh {
key: Tok<String>,
nonzero: bool,
},
Scan {
left: Box<VecMatcher>,
sep: Vec<ScalMatcher>,
right: Box<VecMatcher>,
/// The separator traverses the sequence towards this side
direction: Side,
},
Middle {
/// Matches the left outer region
left: Box<VecMatcher>,
/// Matches the left separator
left_sep: Vec<ScalMatcher>,
/// Matches the middle - can only ever be a plain placeholder
mid: Box<VecMatcher>,
/// Matches the right separator
right_sep: Vec<ScalMatcher>,
/// Matches the right outer region
right: Box<VecMatcher>,
/// Order of significance for sorting equally good projects based on
/// the length of matches on either side.
///
/// Vectorial keys that appear on either side, in priority order
key_order: Vec<Tok<String>>,
},
}
pub enum AnyMatcher {
Scalar(Vec<ScalMatcher>),
Vec { left: Vec<ScalMatcher>, mid: VecMatcher, right: Vec<ScalMatcher> },
}
// ################ Display ################
impl fmt::Display for ScalMatcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Placeh { key } => write!(f, "${key}"),
Self::Name(n) => write!(f, "{n}"),
Self::S(t, body) => {
let (l, r, _) = PARENS.iter().find(|r| r.2 == *t).unwrap();
write!(f, "{l}{body}{r}")
},
Self::Lambda(arg, body) => write!(f, "\\{arg}.{body}"),
}
}
}
impl fmt::Display for VecMatcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Placeh { key, nonzero: true } => write!(f, "...${key}"),
Self::Placeh { key, nonzero: false } => write!(f, "..${key}"),
Self::Scan { left, sep, right, direction } => {
let arrow = if direction == &Side::Left { "<==" } else { "==>" };
write!(f, "Scan{{{left} {arrow} {} {arrow} {right}}}", sep.iter().join(" "))
},
Self::Middle { left, left_sep, mid, right_sep, right, .. } => {
let left_sep_s = left_sep.iter().join(" ");
let right_sep_s = right_sep.iter().join(" ");
write!(f, "Middle{{{left}|{left_sep_s}|{mid}|{right_sep_s}|{right}}}")
},
}
}
}
impl fmt::Display for AnyMatcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Scalar(s) => {
write!(f, "({})", s.iter().join(" "))
},
Self::Vec { left, mid, right } => {
let lefts = left.iter().join(" ");
let rights = right.iter().join(" ");
write!(f, "[{lefts}|{mid}|{rights}]")
},
}
}
}
// ################ External ################
/// A priority-order tree of the vectorial placeholders with scalars as leaves.
pub struct Matcher(AnyMatcher);
impl Matcher {
pub fn new(pattern: &[MacTree]) -> Self { Self(mk_any(pattern)) }
pub fn apply<'a>(&self, seq: &'a [MacTree], save_loc: &impl Fn(Sym) -> bool) -> Option<MatchState<'a>> {
any_match(&self.0, seq, save_loc)
}
}
impl fmt::Display for Matcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) }
}

View File

@@ -0,0 +1,84 @@
use std::sync::Arc;
use hashbrown::HashMap;
use orchid_api::PhKind;
use orchid_base::tree::Ph;
use orchid_base::{interner::Tok, join::join_maps};
use orchid_base::location::Pos;
use crate::macros::{MacTok, MacTree};
use orchid_base::name::Sym;
#[derive(Clone, Copy, Debug)]
pub enum StateEntry<'a> {
Vec(&'a [MacTree]),
Scalar(&'a MacTree),
}
#[derive(Clone)]
pub struct MatchState<'a> {
placeholders: HashMap<Tok<String>, StateEntry<'a>>,
name_posv: HashMap<Sym, Vec<Pos>>,
}
impl<'a> MatchState<'a> {
pub fn from_ph(key: Tok<String>, entry: StateEntry<'a>) -> Self {
Self { placeholders: HashMap::from([(key, entry)]), name_posv: HashMap::new() }
}
pub fn combine(self, s: Self) -> Self {
Self {
placeholders: self.placeholders.into_iter().chain(s.placeholders).collect(),
name_posv: join_maps(self.name_posv, s.name_posv, |_, l, r| {
l.into_iter().chain(r).collect()
}),
}
}
pub fn ph_len(&self, key: &Tok<String>) -> Option<usize> {
match self.placeholders.get(key)? {
StateEntry::Vec(slc) => Some(slc.len()),
_ => None,
}
}
pub fn from_name(name: Sym, location: Pos) -> Self {
Self { name_posv: HashMap::from([(name, vec![location])]), placeholders: HashMap::new() }
}
}
impl Default for MatchState<'static> {
fn default() -> Self { Self { name_posv: HashMap::new(), placeholders: HashMap::new() } }
}
#[must_use]
pub fn apply_exprv(template: &[MacTree], state: &MatchState) -> Vec<MacTree> {
template.iter().map(|e| apply_expr(e, state)).flat_map(Vec::into_iter).collect()
}
#[must_use]
pub fn apply_expr(template: &MacTree, state: &MatchState) -> Vec<MacTree> {
let MacTree { pos, tok } = template;
match &**tok {
MacTok::Name(n) => match state.name_posv.get(n) {
None => vec![template.clone()],
Some(locs) => vec![MacTree { tok: tok.clone(), pos: locs[0].clone() }],
},
MacTok::Atom(_) => vec![template.clone()],
MacTok::S(c, body) => vec![MacTree {
pos: pos.clone(), tok: Arc::new(MacTok::S(*c, apply_exprv(body.as_slice(), state))),
}],
MacTok::Ph(Ph { name, kind }) => {
let Some(value) = state.placeholders.get(name) else {
panic!("Placeholder does not have a value in state")
};
match (kind, value) {
(PhKind::Scalar, StateEntry::Scalar(item)) => vec![(*item).clone()],
(PhKind::Vector { .. }, StateEntry::Vec(chunk)) => chunk.to_vec(),
_ => panic!("Type mismatch between template and state"),
}
},
MacTok::Lambda(arg, body) => vec![MacTree {
pos: pos.clone(),
tok: Arc::new(MacTok::Lambda(
apply_exprv(arg, state),
apply_exprv(&body[..], state),
)),
}],
MacTok::Slot(_) | MacTok::Ref(_) => panic!("Extension-only variants"),
}
}

View File

@@ -0,0 +1,16 @@
use orchid_api::PhKind;
use orchid_base::interner::Tok;
use orchid_base::tree::Ph;
use crate::macros::{MacTok, MacTree};
/// Returns the name, priority and at_least_one of the expression if it is
/// a vectorial placeholder
#[must_use]
pub fn vec_attrs(expr: &MacTree) -> Option<(Tok<String>, u8, bool)> {
match (*expr.tok).clone() {
MacTok::Ph(Ph { kind: PhKind::Vector { priority, at_least_one }, name }) =>
Some((name, priority, at_least_one)),
_ => None,
}
}

View File

@@ -0,0 +1,94 @@
use std::cmp::Ordering;
use itertools::Itertools;
use super::scal_match::scalv_match;
use super::shared::VecMatcher;
use orchid_base::name::Sym;
use crate::{macros::MacTree, rule::state::{MatchState, StateEntry}};
#[must_use]
pub fn vec_match<'a>(
matcher: &VecMatcher,
seq: &'a [MacTree],
save_loc: &impl Fn(Sym) -> bool,
) -> Option<MatchState<'a>> {
match matcher {
VecMatcher::Placeh { key, nonzero } => {
if *nonzero && seq.is_empty() {
return None;
}
return Some(MatchState::from_ph(key.clone(), StateEntry::Vec(seq)));
},
VecMatcher::Scan { left, sep, right, direction } => {
if seq.len() < sep.len() {
return None;
}
for lpos in direction.walk(0..=seq.len() - sep.len()) {
let rpos = lpos + sep.len();
let state = vec_match(left, &seq[..lpos], save_loc)
.and_then(|s| Some(s.combine(scalv_match(sep, &seq[lpos..rpos], save_loc)?)))
.and_then(|s| Some(s.combine(vec_match(right, &seq[rpos..], save_loc)?)));
if let Some(s) = state {
return Some(s);
}
}
None
},
// XXX predict heap space usage and allocation count
VecMatcher::Middle { left, left_sep, mid, right_sep, right, key_order } => {
if seq.len() < left_sep.len() + right_sep.len() {
return None;
}
// Valid locations for the left separator
let lposv = seq[..seq.len() - right_sep.len()]
.windows(left_sep.len())
.enumerate()
.filter_map(|(i, window)| scalv_match(left_sep, window, save_loc).map(|s| (i, s)))
.collect::<Vec<_>>();
// Valid locations for the right separator
let rposv = seq[left_sep.len()..]
.windows(right_sep.len())
.enumerate()
.filter_map(|(i, window)| scalv_match(right_sep, window, save_loc).map(|s| (i, s)))
.collect::<Vec<_>>();
// Valid combinations of locations for the separators
let mut pos_pairs = lposv
.into_iter()
.cartesian_product(rposv)
.filter(|((lpos, _), (rpos, _))| lpos + left_sep.len() <= *rpos)
.map(|((lpos, lstate), (rpos, rstate))| (lpos, rpos, lstate.combine(rstate)))
.collect::<Vec<_>>();
// In descending order of size
pos_pairs.sort_by_key(|(l, r, _)| -((r - l) as i64));
let eql_clusters = pos_pairs.into_iter().chunk_by(|(al, ar, _)| ar - al);
for (_gap_size, cluster) in eql_clusters.into_iter() {
let best_candidate = cluster
.into_iter()
.filter_map(|(lpos, rpos, state)| {
Some(
state
.combine(vec_match(left, &seq[..lpos], save_loc)?)
.combine(vec_match(mid, &seq[lpos + left_sep.len()..rpos], save_loc)?)
.combine(vec_match(right, &seq[rpos + right_sep.len()..], save_loc)?),
)
})
.max_by(|a, b| {
for key in key_order {
let alen = a.ph_len(key).expect("key_order references scalar or missing");
let blen = b.ph_len(key).expect("key_order references scalar or missing");
match alen.cmp(&blen) {
Ordering::Equal => (),
any => return any,
}
}
Ordering::Equal
});
if let Some(state) = best_candidate {
return Some(state);
}
}
None
},
}
}

View File

@@ -3,7 +3,6 @@ use std::path::PathBuf;
use std::sync::Mutex;
use std::{process, thread};
use orchid_api::ExtensionHeader;
use orchid_api_traits::{Decode, Encode};
use orchid_base::logging::Logger;
use orchid_base::msg::{recv_msg, send_msg};
@@ -15,7 +14,7 @@ pub struct Subprocess {
child: Mutex<process::Child>,
stdin: Mutex<process::ChildStdin>,
stdout: Mutex<process::ChildStdout>,
header: ExtensionHeader,
header: api::ExtensionHeader,
}
impl Subprocess {
pub fn new(mut cmd: process::Command, logger: Logger) -> io::Result<Self> {
@@ -30,7 +29,7 @@ impl Subprocess {
api::HostHeader { log_strategy: logger.strat() }.encode(&mut stdin);
stdin.flush()?;
let mut stdout = child.stdout.take().unwrap();
let header = ExtensionHeader::decode(&mut stdout);
let header = api::ExtensionHeader::decode(&mut stdout);
let child_stderr = child.stderr.take().unwrap();
thread::Builder::new().name(format!("stderr-fwd:{prog}")).spawn(move || {
let mut reader = io::BufReader::new(child_stderr);

View File

@@ -4,9 +4,9 @@ use std::sync::{Mutex, OnceLock};
use itertools::Itertools;
use never::Never;
use orchid_base::error::OrcRes;
use orchid_base::interner::{deintern, intern, Tok};
use orchid_base::interner::{intern, Tok};
use orchid_base::location::Pos;
use orchid_base::macros::{mtreev_from_api, MTree};
use orchid_base::macros::mtreev_from_api;
use orchid_base::name::Sym;
use orchid_base::parse::{Comment, Import};
use orchid_base::tree::{TokTree, Token};
@@ -16,6 +16,7 @@ use substack::{with_iter_stack, Substack};
use crate::api;
use crate::expr::Expr;
use crate::extension::{AtomHand, System};
use crate::macros::{MacTok, MacTree};
pub type ParsTokTree = TokTree<'static, AtomHand, Never>;
pub type ParsTok = Token<'static, AtomHand, Never>;
@@ -36,7 +37,7 @@ pub enum ItemKind {
}
impl Item {
pub fn from_api<'a>(
pub fn from_api(
tree: api::Item,
path: Substack<Tok<String>>,
sys: &System
@@ -44,12 +45,12 @@ impl Item {
let kind = match tree.kind {
api::ItemKind::Member(m) => ItemKind::Member(Member::from_api(m, path, sys)),
api::ItemKind::Import(i) =>
ItemKind::Import(Import{ path: Sym::deintern(i).iter().collect(), name: None }),
api::ItemKind::Export(e) => ItemKind::Export(deintern(e)),
ItemKind::Import(Import{ path: Sym::from_api(i).iter().collect(), name: None }),
api::ItemKind::Export(e) => ItemKind::Export(Tok::from_api(e)),
api::ItemKind::Macro(api::MacroBlock { priority, rules }) => ItemKind::Macro(priority, {
Vec::from_iter(rules.into_iter().map(|api| Rule {
pos: Pos::from_api(&api.location),
pattern: mtreev_from_api(&api.pattern),
pattern: mtreev_from_api(&api.pattern, &mut |a| MacTok::Atom(AtomHand::from_api(a.clone()))),
kind: RuleKind::Remote(sys.clone(), api.id),
comments: api.comments.iter().map(Comment::from_api).collect_vec()
}))
@@ -67,19 +68,19 @@ pub struct Member {
pub lazy: Mutex<Option<LazyMemberHandle>>,
}
impl Member {
pub fn from_api<'a>(
pub fn from_api(
api: api::Member,
path: Substack<Tok<String>>,
sys: &System,
) -> Self {
let name = deintern(api.name);
let name = Tok::from_api(api.name);
let full_path = path.push(name.clone());
let kind = match api.kind {
api::MemberKind::Lazy(id) =>
return LazyMemberHandle(id, sys.clone(), intern(&full_path.unreverse())).to_member(name),
return LazyMemberHandle(id, sys.clone(), intern(&full_path.unreverse())).into_member(name),
api::MemberKind::Const(c) => MemberKind::Const(Code::from_expr(
CodeLocator::to_const(full_path.unreverse()),
Expr::from_api(c, &mut ())
Expr::from_api(&c, &mut ())
)),
api::MemberKind::Module(m) => MemberKind::Mod(Module::from_api(m, full_path, sys)),
};
@@ -129,7 +130,7 @@ impl LazyMemberHandle {
pub fn run(self) -> OrcRes<MemberKind> {
match self.1.get_tree(self.0) {
api::MemberKind::Const(c) => Ok(MemberKind::Const(Code {
bytecode: Expr::from_api(c, &mut ()).into(),
bytecode: Expr::from_api(&c, &mut ()).into(),
locator: CodeLocator { steps: self.2, rule_loc: None },
source: None,
})),
@@ -139,7 +140,7 @@ impl LazyMemberHandle {
api::MemberKind::Lazy(id) => Self(id, self.1, self.2).run(),
}
}
pub fn to_member(self, name: Tok<String>) -> Member {
pub fn into_member(self, name: Tok<String>) -> Member {
Member { name, kind: OnceLock::new(), lazy: Mutex::new(Some(self)) }
}
}
@@ -148,7 +149,7 @@ impl LazyMemberHandle {
pub struct Rule {
pub pos: Pos,
pub comments: Vec<Comment>,
pub pattern: Vec<MTree<'static>>,
pub pattern: Vec<MacTree>,
pub kind: RuleKind,
}