New plans for macros

About to move them completely to stdlib
This commit is contained in:
2024-08-18 22:57:06 +02:00
parent 11951ede43
commit 3a63894de2
78 changed files with 2557 additions and 1980 deletions

View File

@@ -1,7 +1,9 @@
use std::{fmt, ops::RangeInclusive};
use std::fmt;
use std::ops::RangeInclusive;
use itertools::Itertools;
use orchid_api::parser::CharFilter;
use crate::api;
pub type CRange = RangeInclusive<char>;
@@ -11,7 +13,7 @@ pub trait ICFilter: fmt::Debug {
impl ICFilter for [RangeInclusive<char>] {
fn ranges(&self) -> &[RangeInclusive<char>] { self }
}
impl ICFilter for CharFilter {
impl ICFilter for api::CharFilter {
fn ranges(&self) -> &[RangeInclusive<char>] { &self.0 }
}
@@ -24,8 +26,8 @@ fn try_merge_char_ranges(left: CRange, right: CRange) -> Result<CRange, (CRange,
/// Process the character ranges to make them adhere to the structural
/// requirements of [CharFilter]
pub fn mk_char_filter(items: impl IntoIterator<Item = CRange>) -> CharFilter {
CharFilter(
pub fn mk_char_filter(items: impl IntoIterator<Item = CRange>) -> api::CharFilter {
api::CharFilter(
(items.into_iter())
.filter(|r| *r.start() as u32 <= *r.end() as u32)
.sorted_by_key(|r| *r.start() as u32)
@@ -37,16 +39,20 @@ pub fn mk_char_filter(items: impl IntoIterator<Item = CRange>) -> CharFilter {
/// Decide whether a char filter matches a character via binary search
pub fn char_filter_match(cf: &(impl ICFilter + ?Sized), c: char) -> bool {
match cf.ranges().binary_search_by_key(&c, |l| *l.end()) {
Ok(_) => true, // c is the end of a range
Ok(_) => true, // c is the end of a range
Err(i) if i == cf.ranges().len() => false, // all ranges end before c
Err(i) => cf.ranges()[i].contains(&c), // c between cf.0[i-1]?.end and cf.0[i].end, check [i]
Err(i) => cf.ranges()[i].contains(&c), /* c between cf.0[i-1]?.end and cf.0[i].end,
* check [i] */
}
}
/// Merge two char filters into a filter that matches if either of the
/// constituents would match.
pub fn char_filter_union(l: &(impl ICFilter + ?Sized), r: &(impl ICFilter + ?Sized)) -> CharFilter {
CharFilter(
pub fn char_filter_union(
l: &(impl ICFilter + ?Sized),
r: &(impl ICFilter + ?Sized),
) -> api::CharFilter {
api::CharFilter(
(l.ranges().iter().merge_by(r.ranges(), |l, r| l.start() <= r.start()))
.cloned()
.coalesce(try_merge_char_ranges)

View File

@@ -1,28 +1,29 @@
use std::sync::Arc;
use orchid_api::error::{ProjErr, ProjErrLocation};
use itertools::Itertools;
use crate::interner::{deintern, Tok};
use crate::location::Pos;
use crate::api;
/// A point of interest in resolving the error, such as the point where
/// processing got stuck, a command that is likely to be incorrect
#[derive(Clone, Debug)]
pub struct ErrorPosition {
pub struct ErrPos {
/// The suspected origin
pub position: Pos,
/// Any information about the role of this origin
pub message: Option<Arc<String>>,
}
impl ErrorPosition {
pub fn from_api(pel: &ProjErrLocation) -> Self {
impl ErrPos {
pub fn from_api(pel: &api::ErrLocation) -> Self {
Self {
message: Some(pel.message.clone()).filter(|s| !s.is_empty()),
position: Pos::from_api(&pel.location),
}
}
pub fn to_api(&self) -> ProjErrLocation {
ProjErrLocation {
pub fn to_api(&self) -> api::ErrLocation {
api::ErrLocation {
message: self.message.clone().unwrap_or_default(),
location: self.position.to_api(),
}
@@ -31,25 +32,62 @@ impl ErrorPosition {
Self { message: Some(Arc::new(msg.to_string())), position }
}
}
impl From<Pos> for ErrorPosition {
impl From<Pos> for ErrPos {
fn from(origin: Pos) -> Self { Self { position: origin, message: None } }
}
#[derive(Clone, Debug)]
pub struct OwnedError {
pub struct OrcErr {
pub description: Tok<String>,
pub message: Arc<String>,
pub positions: Vec<ErrorPosition>,
pub positions: Vec<ErrPos>,
}
impl OwnedError {
pub fn from_api(err: &ProjErr) -> Self {
impl OrcErr {
pub fn from_api(err: &api::OrcError) -> Self {
Self {
description: deintern(err.description),
message: err.message.clone(),
positions: err.locations.iter().map(ErrorPosition::from_api).collect(),
positions: err.locations.iter().map(ErrPos::from_api).collect(),
}
}
pub fn from_apiv(err: Vec<ProjErr>) -> Vec<Self> {
err.into_iter().map(|e| Self::from_api(&e)).collect()
pub fn to_api(&self) -> api::OrcError {
api::OrcError {
description: self.description.marker(),
message: self.message.clone(),
locations: self.positions.iter().map(ErrPos::to_api).collect(),
}
}
}
impl Eq for OrcErr {}
impl PartialEq for OrcErr {
fn eq(&self, other: &Self) -> bool { self.description == other.description }
}
impl From<OrcErr> for Vec<OrcErr> {
fn from(value: OrcErr) -> Self { vec![value] }
}
pub fn errv_to_apiv<'a>(errv: impl IntoIterator<Item = &'a OrcErr>) -> Vec<api::OrcError> {
errv.into_iter().map(OrcErr::to_api).collect_vec()
}
pub fn errv_from_apiv<'a>(err: impl IntoIterator<Item = &'a api::OrcError>) -> Vec<OrcErr> {
err.into_iter().map(OrcErr::from_api).collect()
}
pub type OrcRes<T> = Result<T, Vec<OrcErr>>;
pub fn mk_err(
description: Tok<String>,
message: impl AsRef<str>,
posv: impl IntoIterator<Item = ErrPos>,
) -> OrcErr {
OrcErr {
description,
message: Arc::new(message.as_ref().to_string()),
positions: posv.into_iter().collect(),
}
}
pub trait Reporter {
fn report(&self, e: OrcErr);
}

View File

@@ -1,8 +1,7 @@
use std::any::TypeId;
use itertools::Itertools;
use orchid_api::expr::{Clause, Expr};
use orchid_api::location::Location;
use crate::api;
use super::traits::{GenClause, Generable};
use crate::intern::{deintern, intern};

View File

@@ -1,7 +1,7 @@
//! Various elemental components to build expression trees that all implement
//! [GenClause].
use orchid_api::atom::Atom;
use crate::api;
use super::traits::{GenClause, Generable};

View File

@@ -5,7 +5,7 @@ use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt;
use orchid_api::atom::Atom;
use crate::api;
/// Representations of the Orchid expression tree that can describe basic
/// language elements.

View File

@@ -4,8 +4,7 @@
use std::fmt;
use dyn_clone::{clone_box, DynClone};
use orchid_api::atom::Atom;
use orchid_api::expr::Expr;
use crate::api;
use trait_set::trait_set;
use super::tpl;

View File

@@ -25,9 +25,7 @@ impl<T> IdStore<T> {
if tbl_g.contains_key(&id64) { Some(IdRecord(id64, tbl_g)) } else { None }
}
pub fn is_empty(&self) -> bool { self.len() == 0 }
pub fn len(&self) -> usize {
self.table.get().map(|t| t.lock().unwrap().len()).unwrap_or(0)
}
pub fn len(&self) -> usize { self.table.get().map(|t| t.lock().unwrap().len()).unwrap_or(0) }
}
impl<T> Default for IdStore<T> {

View File

@@ -7,11 +7,9 @@ use std::{fmt, hash};
use hashbrown::{HashMap, HashSet};
use itertools::Itertools as _;
use orchid_api::interner::{
ExternStr, ExternStrv, IntReq, InternStr, InternStrv, Retained, TStr, TStrv,
};
use orchid_api_traits::Request;
use orchid_api_traits::{Encode, Decode, Request};
use crate::api;
use crate::reqnot::{DynRequester, Requester};
/// Clippy crashes while verifying `Tok: Sized` without this and I cba to create
@@ -57,11 +55,21 @@ impl<T: Interned + fmt::Debug> fmt::Debug for Tok<T> {
write!(f, "Token({} -> {:?})", self.marker().get_id(), self.data.as_ref())
}
}
impl<T: Interned + Encode> Encode for Tok<T> {
fn encode<W: std::io::Write + ?Sized>(&self, write: &mut W) { self.data.encode(write) }
}
impl<T: Interned + Decode> Decode for Tok<T> {
fn decode<R: std::io::Read + ?Sized>(read: &mut R) -> Self {
intern(&T::decode(read))
}
}
pub trait Interned: Eq + hash::Hash + Clone {
pub trait Interned: Eq + hash::Hash + Clone + Internable<Interned = Self> {
type Marker: InternMarker<Interned = Self> + Sized;
fn intern(self: Arc<Self>, req: &(impl DynRequester<Transfer = IntReq> + ?Sized))
-> Self::Marker;
fn intern(
self: Arc<Self>,
req: &(impl DynRequester<Transfer = api::IntReq> + ?Sized),
) -> Self::Marker;
fn bimap(interner: &mut TypedInterners) -> &mut Bimap<Self>;
}
@@ -72,26 +80,32 @@ pub trait Internable {
pub trait InternMarker: Copy + PartialEq + Eq + PartialOrd + Ord + hash::Hash + Sized {
type Interned: Interned<Marker = Self>;
fn resolve(self, req: &(impl DynRequester<Transfer = IntReq> + ?Sized)) -> Tok<Self::Interned>;
fn resolve(
self,
req: &(impl DynRequester<Transfer = api::IntReq> + ?Sized),
) -> Tok<Self::Interned>;
fn get_id(self) -> NonZeroU64;
fn from_id(id: NonZeroU64) -> Self;
}
impl Interned for String {
type Marker = TStr;
type Marker = api::TStr;
fn intern(
self: Arc<Self>,
req: &(impl DynRequester<Transfer = IntReq> + ?Sized),
req: &(impl DynRequester<Transfer = api::IntReq> + ?Sized),
) -> Self::Marker {
req.request(InternStr(self))
req.request(api::InternStr(self))
}
fn bimap(interners: &mut TypedInterners) -> &mut Bimap<Self> { &mut interners.strings }
}
impl InternMarker for TStr {
impl InternMarker for api::TStr {
type Interned = String;
fn resolve(self, req: &(impl DynRequester<Transfer = IntReq> + ?Sized)) -> Tok<Self::Interned> {
Tok::new(req.request(ExternStr(self)), self)
fn resolve(
self,
req: &(impl DynRequester<Transfer = api::IntReq> + ?Sized),
) -> Tok<Self::Interned> {
Tok::new(req.request(api::ExternStr(self)), self)
}
fn get_id(self) -> NonZeroU64 { self.0 }
fn from_id(id: NonZeroU64) -> Self { Self(id) }
@@ -108,20 +122,24 @@ impl Internable for String {
}
impl Interned for Vec<Tok<String>> {
type Marker = TStrv;
type Marker = api::TStrv;
fn intern(
self: Arc<Self>,
req: &(impl DynRequester<Transfer = IntReq> + ?Sized),
req: &(impl DynRequester<Transfer = api::IntReq> + ?Sized),
) -> Self::Marker {
req.request(InternStrv(Arc::new(self.iter().map(|t| t.marker()).collect())))
req.request(api::InternStrv(Arc::new(self.iter().map(|t| t.marker()).collect())))
}
fn bimap(interners: &mut TypedInterners) -> &mut Bimap<Self> { &mut interners.vecs }
}
impl InternMarker for TStrv {
impl InternMarker for api::TStrv {
type Interned = Vec<Tok<String>>;
fn resolve(self, req: &(impl DynRequester<Transfer = IntReq> + ?Sized)) -> Tok<Self::Interned> {
let data = Arc::new(req.request(ExternStrv(self)).iter().map(|m| deintern(*m)).collect_vec());
fn resolve(
self,
req: &(impl DynRequester<Transfer = api::IntReq> + ?Sized),
) -> Tok<Self::Interned> {
let data =
Arc::new(req.request(api::ExternStrv(self)).iter().map(|m| deintern(*m)).collect_vec());
Tok::new(data, self)
}
fn get_id(self) -> NonZeroU64 { self.0 }
@@ -138,14 +156,14 @@ impl Internable for Vec<Tok<String>> {
fn get_owned(&self) -> Arc<Self::Interned> { Arc::new(self.to_vec()) }
}
impl Internable for Vec<TStr> {
impl Internable for Vec<api::TStr> {
type Interned = Vec<Tok<String>>;
fn get_owned(&self) -> Arc<Self::Interned> {
Arc::new(self.iter().map(|ts| deintern(*ts)).collect())
}
}
impl Internable for [TStr] {
impl Internable for [api::TStr] {
type Interned = Vec<Tok<String>>;
fn get_owned(&self) -> Arc<Self::Interned> {
Arc::new(self.iter().map(|ts| deintern(*ts)).collect())
@@ -157,7 +175,7 @@ const BASE_RC: usize = 3;
#[test]
fn base_rc_correct() {
let tok = Tok::new(Arc::new("foo".to_string()), TStr(1.try_into().unwrap()));
let tok = Tok::new(Arc::new("foo".to_string()), api::TStr(1.try_into().unwrap()));
let mut bimap = Bimap::default();
bimap.insert(tok.clone());
assert_eq!(Arc::strong_count(&tok.data), BASE_RC + 1, "the bimap plus the current instance");
@@ -214,7 +232,7 @@ pub struct TypedInterners {
#[derive(Default)]
pub struct Interner {
interners: TypedInterners,
master: Option<Box<dyn DynRequester<Transfer = IntReq>>>,
master: Option<Box<dyn DynRequester<Transfer = api::IntReq>>>,
}
static ID: atomic::AtomicU64 = atomic::AtomicU64::new(1);
@@ -236,7 +254,7 @@ pub fn interner() -> impl DerefMut<Target = Interner> {
G(g)
}
pub fn init_replica(req: impl DynRequester<Transfer = IntReq> + 'static) {
pub fn init_replica(req: impl DynRequester<Transfer = api::IntReq> + 'static) {
let mut g = INTERNER.lock().unwrap();
assert!(g.is_none(), "Attempted to initialize replica interner after first use");
*g = Some(Interner {
@@ -273,15 +291,18 @@ pub fn deintern<M: InternMarker>(marker: M) -> Tok<M::Interned> {
token
}
pub fn merge_retained(into: &mut Retained, from: &Retained) {
pub fn merge_retained(into: &mut api::Retained, from: &api::Retained) {
into.strings = into.strings.iter().chain(&from.strings).copied().unique().collect();
into.vecs = into.vecs.iter().chain(&from.vecs).copied().unique().collect();
}
pub fn sweep_replica() -> Retained {
pub fn sweep_replica() -> api::Retained {
let mut g = interner();
assert!(g.master.is_some(), "Not a replica");
Retained { strings: g.interners.strings.sweep_replica(), vecs: g.interners.vecs.sweep_replica() }
api::Retained {
strings: g.interners.strings.sweep_replica(),
vecs: g.interners.vecs.sweep_replica(),
}
}
/// Create a thread-local token instance and copy it. This ensures that the
@@ -301,14 +322,7 @@ macro_rules! intern {
}};
}
pub fn sweep_master(retained: Retained) {
eprintln!(
"Hello, {:?}",
intern!([Tok<String>]: &[
intern!(str: "bar"),
intern!(str: "baz")
])
);
pub fn sweep_master(retained: api::Retained) {
let mut g = interner();
assert!(g.master.is_none(), "Not master");
g.interners.strings.sweep_master(retained.strings.into_iter().collect());
@@ -317,12 +331,12 @@ pub fn sweep_master(retained: Retained) {
#[cfg(test)]
mod test {
use std::num::NonZero;
use orchid_api_traits::{enc_vec, Decode};
use super::*;
use std::num::NonZero;
use orchid_api::interner::TStr;
use orchid_api_traits::{Decode, Encode};
use crate::api;
#[test]
fn test_i() {
@@ -335,10 +349,9 @@ mod test {
#[test]
fn test_coding() {
let coded = TStr(NonZero::new(3u64).unwrap());
let mut enc = &coded.enc_vec()[..];
eprintln!("Coded {enc:?}");
TStr::decode(&mut enc);
assert_eq!(enc, [])
let coded = api::TStr(NonZero::new(3u64).unwrap());
let mut enc = &enc_vec(&coded)[..];
api::TStr::decode(&mut enc);
assert_eq!(enc, [], "Did not consume all of {enc:?}")
}
}

View File

@@ -1,3 +1,5 @@
use orchid_api as api;
pub mod boxed_iter;
pub mod clone;
pub mod combine;
@@ -12,10 +14,11 @@ pub mod id_store;
pub mod interner;
pub mod join;
pub mod location;
pub mod logging;
pub mod name;
pub mod number;
pub mod parse;
pub mod reqnot;
pub mod sequence;
pub mod tokens;
pub mod tree;
pub mod logging;

View File

@@ -5,12 +5,11 @@ use std::hash::Hash;
use std::ops::Range;
use std::sync::Arc;
use orchid_api::location as api;
use trait_set::trait_set;
use crate::interner::{deintern, Tok};
use crate::name::Sym;
use crate::sym;
use crate::{api, sym};
trait_set! {
pub trait GetSrc = FnMut(&Sym) -> Tok<String>;

View File

@@ -1,16 +1,30 @@
use std::{fs::File, io::Write};
use std::fmt::Arguments;
use std::fs::File;
use std::io::{stderr, Write};
pub use orchid_api::logging::LogStrategy;
pub use api::LogStrategy;
use itertools::Itertools;
pub struct Logger(LogStrategy);
use crate::api;
#[derive(Clone)]
pub struct Logger(api::LogStrategy);
impl Logger {
pub fn new(strat: LogStrategy) -> Self { Self(strat) }
pub fn log(&self, msg: String) {
match &self.0 {
LogStrategy::StdErr => eprintln!("{msg}"),
LogStrategy::File(f) => writeln!(File::open(f).unwrap(), "{msg}").unwrap(),
pub fn new(strat: api::LogStrategy) -> Self { Self(strat) }
pub fn log(&self, msg: impl AsRef<str>) { writeln!(self, "{}", msg.as_ref()) }
pub fn strat(&self) -> api::LogStrategy { self.0.clone() }
pub fn log_buf(&self, event: impl AsRef<str>, buf: &[u8]) {
if !std::env::var("ORCHID_LOG_BUFFERS").unwrap().is_empty() {
writeln!(self, "{}: [{}]", event.as_ref(), buf.iter().map(|b| format!("{b:02x}")).join(" "))
}
}
pub fn write_fmt(&self, fmt: Arguments) {
match &self.0 {
api::LogStrategy::StdErr => stderr().write_fmt(fmt).expect("Could not write to stderr!"),
api::LogStrategy::File(f) => {
let mut file = File::open(f).expect("Could not open logfile");
file.write_fmt(fmt).expect("Could not write to logfile");
},
}
}
pub fn strat(&self) -> LogStrategy { self.0.clone() }
}

View File

@@ -1,7 +1,6 @@
use orchid_api_traits::Decode;
use std::io;
use orchid_api_traits::Encode;
use orchid_api_traits::{Decode, Encode};
pub fn send_msg(write: &mut impl io::Write, msg: &[u8]) -> io::Result<()> {
u32::try_from(msg.len()).unwrap().encode(write);
@@ -14,4 +13,4 @@ pub fn recv_msg(read: &mut impl io::Read) -> io::Result<Vec<u8>> {
let mut msg = vec![0u8; len as usize];
read.read_exact(&mut msg)?;
Ok(msg)
}
}

View File

@@ -4,14 +4,15 @@ use std::borrow::Borrow;
use std::hash::Hash;
use std::iter::Cloned;
use std::num::{NonZeroU64, NonZeroUsize};
use std::ops::{Deref, Index};
use std::ops::{Bound, Deref, Index, RangeBounds};
use std::path::Path;
use std::{fmt, slice, vec};
use itertools::Itertools;
use orchid_api::interner::TStr;
use orchid_api::TStrv;
use trait_set::trait_set;
use crate::api;
use crate::interner::{deintern, intern, InternMarker, Tok};
trait_set! {
@@ -40,11 +41,11 @@ impl PathSlice {
}
/// Find the longest shared prefix of this name and another sequence
pub fn coprefix<'a>(&'a self, other: &PathSlice) -> &'a PathSlice {
&self[0..self.iter().zip(other.iter()).take_while(|(l, r)| l == r).count()]
&self[0..self.iter().zip(other.iter()).take_while(|(l, r)| l == r).count() as u16]
}
/// Find the longest shared suffix of this name and another sequence
pub fn cosuffix<'a>(&'a self, other: &PathSlice) -> &'a PathSlice {
&self[0..self.iter().zip(other.iter()).take_while(|(l, r)| l == r).count()]
&self[0..self.iter().zip(other.iter()).take_while(|(l, r)| l == r).count() as u16]
}
/// Remove another
pub fn strip_prefix<'a>(&'a self, other: &PathSlice) -> Option<&'a PathSlice> {
@@ -52,7 +53,8 @@ impl PathSlice {
(shared == other.len()).then_some(PathSlice::new(&self[shared..]))
}
/// Number of path segments
pub fn len(&self) -> usize { self.0.len() }
pub fn len(&self) -> u16 { self.0.len().try_into().expect("Too long name!") }
pub fn get<I: NameIndex>(&self, index: I) -> Option<&I::Output> { index.get(self) }
/// Whether there are any path segments. In other words, whether this is a
/// valid name
pub fn is_empty(&self) -> bool { self.len() == 0 }
@@ -79,31 +81,47 @@ impl<'a> IntoIterator for &'a PathSlice {
fn into_iter(self) -> Self::IntoIter { self.0.iter().cloned() }
}
pub trait NameIndex {
type Output: ?Sized;
fn get(self, name: &PathSlice) -> Option<&Self::Output>;
}
impl<T: NameIndex> Index<T> for PathSlice {
type Output = T::Output;
fn index(&self, index: T) -> &Self::Output { index.get(self).expect("Index out of bounds") }
}
mod idx_impls {
use std::ops;
use super::PathSlice;
use super::{NameIndex, PathSlice, conv_range};
use crate::interner::Tok;
impl ops::Index<usize> for PathSlice {
impl NameIndex for u16 {
type Output = Tok<String>;
fn index(&self, index: usize) -> &Self::Output { &self.0[index] }
fn get(self, name: &PathSlice) -> Option<&Self::Output> { name.0.get(self as usize) }
}
impl NameIndex for ops::RangeFull {
type Output = PathSlice;
fn get(self, name: &PathSlice) -> Option<&Self::Output> { Some(name) }
}
macro_rules! impl_range_index_for_pathslice {
($range:ty) => {
impl ops::Index<$range> for PathSlice {
($range:ident) => {
impl ops::Index<ops::$range<u16>> for PathSlice {
type Output = Self;
fn index(&self, index: $range) -> &Self::Output { Self::new(&self.0[index]) }
fn index(&self, index: ops::$range<u16>) -> &Self::Output {
Self::new(&self.0[conv_range::<u16, usize>(index)])
}
}
};
}
impl_range_index_for_pathslice!(ops::RangeFull);
impl_range_index_for_pathslice!(ops::RangeFrom<usize>);
impl_range_index_for_pathslice!(ops::RangeTo<usize>);
impl_range_index_for_pathslice!(ops::Range<usize>);
impl_range_index_for_pathslice!(ops::RangeInclusive<usize>);
impl_range_index_for_pathslice!(ops::RangeToInclusive<usize>);
impl_range_index_for_pathslice!(RangeFrom);
impl_range_index_for_pathslice!(RangeTo);
impl_range_index_for_pathslice!(Range);
impl_range_index_for_pathslice!(RangeInclusive);
impl_range_index_for_pathslice!(RangeToInclusive);
}
impl Deref for PathSlice {
@@ -120,6 +138,18 @@ impl<const N: usize> Borrow<PathSlice> for [Tok<String>; N] {
impl Borrow<PathSlice> for Vec<Tok<String>> {
fn borrow(&self) -> &PathSlice { PathSlice::new(&self[..]) }
}
pub fn conv_bound<T: Into<U> + Clone, U>(bound: Bound<&T>) -> Bound<U> {
match bound {
Bound::Included(i) => Bound::Included(i.clone().into()),
Bound::Excluded(i) => Bound::Excluded(i.clone().into()),
Bound::Unbounded => Bound::Unbounded,
}
}
pub fn conv_range<'a, T: Into<U> + Clone + 'a, U: 'a>(
range: impl RangeBounds<T>
) -> (Bound<U>, Bound<U>) {
(conv_bound(range.start_bound()), conv_bound(range.end_bound()))
}
/// A token path which may be empty. [VName] is the non-empty,
/// [PathSlice] is the borrowed version
@@ -227,7 +257,7 @@ impl VName {
let data: Vec<_> = items.into_iter().collect();
if data.is_empty() { Err(EmptyNameError) } else { Ok(Self(data)) }
}
pub fn deintern(items: impl IntoIterator<Item = TStr>) -> Result<Self, EmptyNameError> {
pub fn deintern(items: impl IntoIterator<Item = api::TStr>) -> Result<Self, EmptyNameError> {
Self::new(items.into_iter().map(deintern))
}
/// Unwrap the enclosed vector
@@ -327,6 +357,9 @@ impl Sym {
pub fn id(&self) -> NonZeroU64 { self.0.marker().get_id() }
/// Extern the sym for editing
pub fn to_vname(&self) -> VName { VName(self[..].to_vec()) }
pub fn deintern(marker: TStrv) -> Sym {
Self::from_tok(deintern(marker)).expect("Empty sequence found for serialized Sym")
}
}
impl fmt::Debug for Sym {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Sym({self})") }

View File

@@ -4,6 +4,10 @@ use std::ops::Range;
use ordered_float::NotNan;
use rust_decimal::Decimal;
use crate::error::{mk_err, OrcErr};
use crate::intern;
use crate::location::Pos;
/// A number, either floating point or unsigned int, parsed by Orchid.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Numeric {
@@ -48,6 +52,18 @@ pub struct NumError {
pub kind: NumErrorKind,
}
pub fn num_to_err(NumError { kind, range }: NumError, offset: u32) -> OrcErr {
mk_err(
intern!(str: "Failed to parse number"),
match kind {
NumErrorKind::NaN => "NaN emerged during parsing",
NumErrorKind::InvalidDigit => "non-digit character encountered",
NumErrorKind::Overflow => "The number being described is too large or too accurate",
},
[Pos::Range(offset + range.start as u32..offset + range.end as u32).into()],
)
}
/// Parse a numbre literal out of text
pub fn parse_num(string: &str) -> Result<Numeric, NumError> {
let overflow_err = NumError { range: 0..string.len(), kind: NumErrorKind::Overflow };

286
orchid-base/src/parse.rs Normal file
View File

@@ -0,0 +1,286 @@
use std::ops::{Deref, Range};
use std::sync::Arc;
use std::{fmt, iter};
use itertools::Itertools;
use crate::error::{mk_err, OrcRes, Reporter};
use crate::interner::{deintern, Tok};
use crate::location::Pos;
use crate::name::VPath;
use crate::tree::{AtomInTok, Paren, TokTree, Token};
use crate::{api, intern};
pub fn name_start(c: char) -> bool { c.is_alphabetic() || c == '_' }
pub fn name_char(c: char) -> bool { name_start(c) || c.is_numeric() }
pub fn op_char(c: char) -> bool { !name_char(c) && !c.is_whitespace() && !"()[]{}\\".contains(c) }
pub fn unrep_space(c: char) -> bool { c.is_whitespace() && !"\r\n".contains(c) }
#[derive(Debug)]
pub struct Snippet<'a, 'b, A: AtomInTok, X> {
prev: &'a TokTree<'b, A, X>,
cur: &'a [TokTree<'b, A, X>],
}
impl<'a, 'b, A: AtomInTok, X> Snippet<'a, 'b, A, X> {
pub fn new(prev: &'a TokTree<'b, A, X>, cur: &'a [TokTree<'b, A, X>]) -> Self {
Self { prev, cur }
}
pub fn split_at(self, pos: u32) -> (Self, Self) {
let fst = Self { prev: self.prev, cur: &self.cur[..pos as usize] };
let new_prev = if pos == 0 { self.prev } else { &self.cur[pos as usize - 1] };
let snd = Self { prev: new_prev, cur: &self.cur[pos as usize..] };
(fst, snd)
}
pub fn find_idx(self, mut f: impl FnMut(&Token<'b, A, X>) -> bool) -> Option<u32> {
self.cur.iter().position(|t| f(&t.tok)).map(|t| t as u32)
}
pub fn get(self, idx: u32) -> Option<&'a TokTree<'b, A, X>> { self.cur.get(idx as usize) }
pub fn len(self) -> u32 { self.cur.len() as u32 }
pub fn prev(self) -> &'a TokTree<'b, A, X> { self.prev }
pub fn pos(self) -> Range<u32> {
(self.cur.first().map(|f| f.range.start..self.cur.last().unwrap().range.end))
.unwrap_or(self.prev.range.clone())
}
pub fn pop_front(self) -> Option<(&'a TokTree<'b, A, X>, Self)> {
self.cur.first().map(|r| (r, self.split_at(1).1))
}
pub fn split_once(self, f: impl FnMut(&Token<'b, A, X>) -> bool) -> Option<(Self, Self)> {
let idx = self.find_idx(f)?;
Some((self.split_at(idx).0, self.split_at(idx + 1).1))
}
pub fn split(
mut self,
mut f: impl FnMut(&Token<'b, A, X>) -> bool,
) -> impl Iterator<Item = Self> {
iter::from_fn(move || {
self.is_empty().then_some(())?;
let (ret, next) = self.split_once(&mut f).unwrap_or(self.split_at(self.len()));
self = next;
Some(ret)
})
}
pub fn is_empty(self) -> bool { self.len() == 0 }
pub fn skip_fluff(self) -> Self {
let non_fluff_start = self.find_idx(|t| !matches!(t, Token::NS | Token::Comment(_)));
self.split_at(non_fluff_start.unwrap_or(self.len())).1
}
}
impl<'a, 'b, A: AtomInTok, X> Copy for Snippet<'a, 'b, A, X> {}
impl<'a, 'b, A: AtomInTok, X> Clone for Snippet<'a, 'b, A, X> {
fn clone(&self) -> Self { *self }
}
impl<'a, 'b, A: AtomInTok, X> Deref for Snippet<'a, 'b, A, X> {
type Target = [TokTree<'b, A, X>];
fn deref(&self) -> &Self::Target { self.cur }
}
/// Remove tokens that aren't meaningful in expression context, such as comments
/// or line breaks
pub fn strip_fluff<'a, A: AtomInTok, X: Clone>(
tt: &TokTree<'a, A, X>,
) -> Option<TokTree<'a, A, X>> {
let tok = match &tt.tok {
Token::BR => return None,
Token::Comment(_) => return None,
Token::LambdaHead(arg) => Token::LambdaHead(arg.iter().filter_map(strip_fluff).collect()),
Token::S(p, b) => Token::S(p.clone(), b.iter().filter_map(strip_fluff).collect()),
t => t.clone(),
};
Some(TokTree { tok, range: tt.range.clone() })
}
#[derive(Clone, Debug)]
pub struct Comment {
pub text: Arc<String>,
pub pos: Pos,
}
pub fn line_items<'a, 'b, A: AtomInTok, X>(
snip: Snippet<'a, 'b, A, X>,
) -> Vec<(Vec<Comment>, Snippet<'a, 'b, A, X>)> {
let mut items = Vec::new();
let mut comments = Vec::new();
for mut line in snip.split(|t| matches!(t, Token::BR)) {
match &line.cur {
[TokTree { tok: Token::S(Paren::Round, tokens), .. }] => line.cur = tokens,
[] => continue,
_ => (),
}
match line.find_idx(|t| !matches!(t, Token::Comment(_))) {
None => comments.extend(line.cur),
Some(i) => {
let (cmts, line) = line.split_at(i);
let comments = Vec::from_iter(comments.drain(..).chain(cmts.cur).map(|t| match &t.tok {
Token::Comment(c) => Comment { text: c.clone(), pos: Pos::Range(t.range.clone()) },
_ => unreachable!("All are comments checked above"),
}));
items.push((comments, line));
},
}
}
items
}
pub fn try_pop_no_fluff<'a, 'b, A: AtomInTok, X>(
snip: Snippet<'a, 'b, A, X>,
) -> OrcRes<(&'a TokTree<'b, A, X>, Snippet<'a, 'b, A, X>)> {
snip.skip_fluff().pop_front().ok_or_else(|| {
vec![mk_err(intern!(str: "Unexpected end"), "Pattern ends abruptly", [
Pos::Range(snip.pos()).into()
])]
})
}
pub fn expect_end(snip: Snippet<'_, '_, impl AtomInTok, impl Sized>) -> OrcRes<()> {
match snip.skip_fluff().get(0) {
Some(surplus) => Err(vec![mk_err(
intern!(str: "Extra code after end of line"),
"Code found after the end of the line",
[Pos::Range(surplus.range.clone()).into()],
)]),
None => Ok(()),
}
}
pub fn expect_tok<'a, 'b, A: AtomInTok, X: fmt::Display>(
snip: Snippet<'a, 'b, A, X>, tok: Tok<String>
) -> OrcRes<Snippet<'a, 'b, A, X>> {
let (head, tail) = try_pop_no_fluff(snip)?;
match &head.tok {
Token::Name(n) if *n == tok => Ok(tail),
t => Err(vec![mk_err(
intern!(str: "Expected specific keyword"),
format!("Expected {tok} but found {t}"),
[Pos::Range(head.range.clone()).into()]
)])
}
}
pub fn parse_multiname<'a, 'b, A: AtomInTok, X: fmt::Display>(
ctx: &impl Reporter,
tail: Snippet<'a, 'b, A, X>,
) -> OrcRes<(Vec<CompName>, Snippet<'a, 'b, A, X>)> {
let ret = rec(ctx, tail);
#[allow(clippy::type_complexity)] // it's an internal function
pub fn rec<'a, 'b, A: AtomInTok, X: fmt::Display>(
ctx: &impl Reporter,
tail: Snippet<'a, 'b, A, X>,
) -> OrcRes<(Vec<(Vec<Tok<String>>, Option<Tok<String>>, Pos)>, Snippet<'a, 'b, A, X>)> {
let comma = intern!(str: ",");
let globstar = intern!(str: "*");
let (name, tail) = tail.skip_fluff().pop_front().ok_or_else(|| {
vec![mk_err(
intern!(str: "Expected name"),
"Expected a name, a list of names, or a globstar.",
[Pos::Range(tail.pos()).into()],
)]
})?;
if let Some((Token::NS, tail)) = tail.skip_fluff().pop_front().map(|(tt, s)| (&tt.tok, s)) {
let n = match &name.tok {
Token::Name(n) if n.starts_with(name_start) => Ok(n),
_ => Err(mk_err(intern!(str: "Unexpected name prefix"), "Only names can precede ::", [
Pos::Range(name.range.clone()).into(),
])),
};
match (rec(ctx, tail), n) {
(Err(ev), n) => Err(Vec::from_iter(ev.into_iter().chain(n.err()))),
(Ok((_, tail)), Err(e)) => {
ctx.report(e);
Ok((vec![], tail))
},
(Ok((n, tail)), Ok(pre)) =>
Ok((n.into_iter().update(|i| i.0.push(pre.clone())).collect_vec(), tail)),
}
} else {
let names = match &name.tok {
Token::Name(ntok) => {
let nopt = match ntok {
n if *n == globstar => None,
n if n.starts_with(op_char) =>
return Err(vec![mk_err(
intern!(str: "Unescaped operator in multiname"),
"Operators in multinames should be enclosed in []",
[Pos::Range(name.range.clone()).into()],
)]),
n => Some(n.clone()),
};
vec![(vec![], nopt, Pos::Range(name.range.clone()))]
},
Token::S(Paren::Square, b) => {
let mut ok = Vec::new();
b.iter().for_each(|tt| match &tt.tok {
Token::Name(n) if n.starts_with(op_char) =>
ok.push((vec![], Some(n.clone()), Pos::Range(tt.range.clone()))),
Token::BR | Token::Comment(_) => (),
_ => ctx.report(mk_err(
intern!(str: "Non-operator in escapement in multiname"),
"In multinames, [] functions as a literal name list reserved for operators",
[Pos::Range(name.range.clone()).into()],
)),
});
ok
},
Token::S(Paren::Round, b) => {
let mut ok = Vec::new();
let body = Snippet::new(name, b);
for csent in body.split(|n| matches!(n, Token::Name(n) if *n == comma)) {
match rec(ctx, csent) {
Err(e) => e.into_iter().for_each(|e| ctx.report(e)),
Ok((v, surplus)) => match surplus.get(0) {
None => ok.extend(v),
Some(t) => ctx.report(mk_err(
intern!(str: "Unexpected token in multiname group"),
"Unexpected token. Likely missing a :: or , or wanted [] instead of ()",
[Pos::Range(t.range.clone()).into()],
)),
},
}
}
ok
},
t =>
return Err(vec![mk_err(
intern!(str: "Unrecognized name end"),
format!("Names cannot end with {t} tokens"),
[Pos::Range(name.range.clone()).into()],
)]),
};
Ok((names, tail))
}
}
ret.map(|(i, tail)| {
let i = Vec::from_iter((i.into_iter()).map(|(p, name, pos)| CompName {
path: VPath::new(p.into_iter().rev()),
name,
pos,
}));
(i, tail)
})
}
/// A compound name, possibly ending with a globstar
#[derive(Debug, Clone)]
pub struct CompName {
pub path: VPath,
pub name: Option<Tok<String>>,
pub pos: Pos,
}
impl CompName {
pub fn from_api(i: api::CompName) -> Self {
Self {
path: VPath::new(i.path.into_iter().map(deintern)),
name: i.name.map(deintern),
pos: Pos::from_api(&i.location),
}
}
}
#[cfg(test)]
mod test {
use never::Never;
use super::Snippet;
fn _covary_snip_a<'a, 'b>(x: Snippet<'static, 'b, Never, ()>) -> Snippet<'a, 'b, Never, ()> { x }
fn _covary_snip_b<'a, 'b>(x: Snippet<'a, 'static, Never, ()>) -> Snippet<'a, 'b, Never, ()> { x }
}

View File

@@ -1,18 +1,20 @@
use std::marker::PhantomData;
use std::{mem, thread};
use std::ops::{BitAnd, Deref};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{sync_channel, SyncSender};
use std::sync::{Arc, Mutex};
use std::{mem, thread};
use dyn_clone::{clone_box, DynClone};
use hashbrown::HashMap;
use orchid_api_traits::{Channel, Coding, Decode, Encode, MsgSet, Request};
use trait_set::trait_set;
pub struct ReplyToken;
trait_set! {
pub trait SendFn<T: MsgSet> = for<'a> FnMut(&'a [u8], ReqNot<T>) + DynClone + Send + 'static;
pub trait ReqFn<T: MsgSet> = FnMut(RequestHandle<T>) + DynClone + Send + Sync + 'static;
pub trait ReqFn<T: MsgSet> = FnMut(RequestHandle<T>) -> ReplyToken + DynClone + Send + Sync + 'static;
pub trait NotifFn<T: MsgSet> =
for<'a> FnMut(<T::In as Channel>::Notif, ReqNot<T>) + DynClone + Send + Sync + 'static;
}
@@ -30,16 +32,17 @@ pub struct RequestHandle<T: MsgSet> {
impl<MS: MsgSet + 'static> RequestHandle<MS> {
pub fn reqnot(&self) -> ReqNot<MS> { self.parent.clone() }
pub fn req(&self) -> &<MS::In as Channel>::Req { &self.message }
fn respond(&self, response: &impl Encode) {
fn respond(&self, response: &impl Encode) -> ReplyToken {
assert!(!self.fulfilled.swap(true, Ordering::Relaxed), "Already responded to {}", self.id);
let mut buf = (!self.id).to_be_bytes().to_vec();
response.encode(&mut buf);
let mut send = clone_box(&*self.reqnot().0.lock().unwrap().send);
(send)(&buf, self.parent.clone());
ReplyToken
}
pub fn handle<T: Request>(&self, _: &T, rep: &T::Response) { self.respond(rep) }
pub fn handle<T: Request>(&self, _: &T, rep: &T::Response) -> ReplyToken { self.respond(rep) }
pub fn will_handle_as<T: Request>(&self, _: &T) -> ReqTypToken<T> { ReqTypToken(PhantomData) }
pub fn handle_as<T: Request>(&self, _token: ReqTypToken<T>, rep: &T::Response) {
pub fn handle_as<T: Request>(&self, _token: ReqTypToken<T>, rep: &T::Response) -> ReplyToken {
self.respond(rep)
}
}
@@ -236,7 +239,7 @@ mod test {
|_, _| panic!("Not receiving notifs"),
|req| {
assert_eq!(req.req(), &TestReq(5));
req.respond(&6u8);
req.respond(&6u8)
},
));
let response = sender.request(TestReq(5));

View File

@@ -1,7 +1,8 @@
use std::fmt::Display;
use orchid_api::tree::{Paren, Placeholder, PlaceholderKind};
pub use api::Paren;
use crate::api;
use crate::interner::{deintern, Tok};
pub const PARENS: &[(char, char, Paren)] =
@@ -10,24 +11,24 @@ pub const PARENS: &[(char, char, Paren)] =
#[derive(Clone, Debug)]
pub struct OwnedPh {
pub name: Tok<String>,
pub kind: PlaceholderKind,
pub kind: api::PlaceholderKind,
}
impl OwnedPh {
pub fn to_api(&self) -> Placeholder {
Placeholder { name: self.name.marker(), kind: self.kind.clone() }
pub fn to_api(&self) -> api::Placeholder {
api::Placeholder { name: self.name.marker(), kind: self.kind.clone() }
}
pub fn from_api(ph: Placeholder) -> Self { Self { name: deintern(ph.name), kind: ph.kind } }
pub fn from_api(ph: api::Placeholder) -> Self { Self { name: deintern(ph.name), kind: ph.kind } }
}
impl Display for OwnedPh {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind {
PlaceholderKind::Name => write!(f, "$_{}", self.name),
PlaceholderKind::Scalar => write!(f, "${}", self.name),
PlaceholderKind::Vector { nz: false, prio: 0 } => write!(f, "..${}", self.name),
PlaceholderKind::Vector { nz: true, prio: 0 } => write!(f, "...${}", self.name),
PlaceholderKind::Vector { nz: false, prio } => write!(f, "..${}:{prio}", self.name),
PlaceholderKind::Vector { nz: true, prio } => write!(f, "...${}:{prio}", self.name),
api::PlaceholderKind::Name => write!(f, "$_{}", self.name),
api::PlaceholderKind::Scalar => write!(f, "${}", self.name),
api::PlaceholderKind::Vector { nz: false, prio: 0 } => write!(f, "..${}", self.name),
api::PlaceholderKind::Vector { nz: true, prio: 0 } => write!(f, "...${}", self.name),
api::PlaceholderKind::Vector { nz: false, prio } => write!(f, "..${}:{prio}", self.name),
api::PlaceholderKind::Vector { nz: true, prio } => write!(f, "...${}:{prio}", self.name),
}
}
}
}

View File

@@ -1,591 +1,266 @@
//! Generic module tree structure
//!
//! Used by various stages of the pipeline with different parameters
use std::fmt;
use std::borrow::Borrow;
use std::cell::RefCell;
use std::fmt::{self, Display, Write};
use std::iter;
use std::marker::PhantomData;
use std::ops::Range;
use std::sync::Arc;
use hashbrown::HashMap;
use itertools::Itertools;
use never::Never;
use substack::Substack;
use trait_set::trait_set;
use crate::boxed_iter::BoxedIter;
use crate::combine::Combine;
use crate::interner::{intern, Tok};
use crate::join::try_join_maps;
use crate::name::{VName, VPath};
use crate::sequence::Sequence;
/// An umbrella trait for operations you can carry out on any part of the tree
/// structure
pub trait TreeTransforms: Sized {
/// Data held at the leaves of the tree
type Item;
/// Data associated with modules
type XMod;
/// Data associated with entries inside modules
type XEnt;
/// Recursive type to enable [TreeTransforms::map_data] to transform the whole
/// tree
type SelfType<T, U, V>: TreeTransforms<Item = T, XMod = U, XEnt = V>;
/// Implementation for [TreeTransforms::map_data]
fn map_data_rec<T, U, V>(
self,
item: &mut impl FnMut(Substack<Tok<String>>, Self::Item) -> T,
module: &mut impl FnMut(Substack<Tok<String>>, Self::XMod) -> U,
entry: &mut impl FnMut(Substack<Tok<String>>, Self::XEnt) -> V,
path: Substack<Tok<String>>,
) -> Self::SelfType<T, U, V>;
/// Transform all the data in the tree without changing its structure
fn map_data<T, U, V>(
self,
mut item: impl FnMut(Substack<Tok<String>>, Self::Item) -> T,
mut module: impl FnMut(Substack<Tok<String>>, Self::XMod) -> U,
mut entry: impl FnMut(Substack<Tok<String>>, Self::XEnt) -> V,
) -> Self::SelfType<T, U, V> {
self.map_data_rec(&mut item, &mut module, &mut entry, Substack::Bottom)
}
/// Visit all elements in the tree. This is like [TreeTransforms::search] but
/// without the early exit
///
/// * init - can be used for reduce, otherwise pass `()`
/// * callback - a callback applied on every module.
/// * [`Substack<Tok<String>>`] - the walked path
/// * [Module] - the current module
/// * `T` - data for reduce.
fn search_all<'a, T>(
&'a self,
init: T,
mut callback: impl FnMut(
Substack<Tok<String>>,
ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>,
T,
) -> T,
) -> T {
let res =
self.search(init, |stack, member, state| Ok::<T, Never>(callback(stack, member, state)));
res.unwrap_or_else(|e| match e {})
}
/// Visit elements in the tree depth first with the provided function
///
/// * init - can be used for reduce, otherwise pass `()`
/// * callback - a callback applied on every module. Can return [Err] to
/// short-circuit the walk
/// * [`Substack<Tok<String>>`] - the walked path
/// * [Module] - the current module
/// * `T` - data for reduce.
fn search<'a, T, E>(
&'a self,
init: T,
mut callback: impl FnMut(
Substack<Tok<String>>,
ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E> {
self.search_rec(init, Substack::Bottom, &mut callback)
}
/// Internal version of [TreeTransforms::search_all]
fn search_rec<'a, T, E>(
&'a self,
state: T,
stack: Substack<Tok<String>>,
callback: &mut impl FnMut(
Substack<Tok<String>>,
ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E>;
}
/// The member in a [ModEntry] which is associated with a name in a [Module]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ModMember<Item, XMod, XEnt> {
/// Arbitrary data
Item(Item),
/// A child module
Sub(Module<Item, XMod, XEnt>),
}
impl<Item, XMod, XEnt> TreeTransforms for ModMember<Item, XMod, XEnt> {
type Item = Item;
type XEnt = XEnt;
type XMod = XMod;
type SelfType<T, U, V> = ModMember<T, U, V>;
fn map_data_rec<T, U, V>(
self,
item: &mut impl FnMut(Substack<Tok<String>>, Item) -> T,
module: &mut impl FnMut(Substack<Tok<String>>, XMod) -> U,
entry: &mut impl FnMut(Substack<Tok<String>>, XEnt) -> V,
path: Substack<Tok<String>>,
) -> Self::SelfType<T, U, V> {
match self {
Self::Item(it) => ModMember::Item(item(path, it)),
Self::Sub(sub) => ModMember::Sub(sub.map_data_rec(item, module, entry, path)),
}
}
fn search_rec<'a, T, E>(
&'a self,
state: T,
stack: Substack<Tok<String>>,
callback: &mut impl FnMut(
Substack<Tok<String>>,
ModMemberRef<'a, Item, XMod, XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E> {
match self {
Self::Item(it) => callback(stack, ModMemberRef::Item(it), state),
Self::Sub(m) => m.search_rec(state, stack, callback),
}
}
}
/// Reasons why merging trees might fail
pub enum ConflictKind<Item: Combine, XMod: Combine, XEnt: Combine> {
/// Error during the merging of items
Item(Item::Error),
/// Error during the merging of module metadata
Module(XMod::Error),
/// Error during the merging of entry metadata
XEnt(XEnt::Error),
/// An item appeared in one tree where the other contained a submodule
ItemModule,
}
macro_rules! impl_for_conflict {
($target:ty, ($($deps:tt)*), $for:ty, $body:tt) => {
impl<Item: Combine, XMod: Combine, XEnt: Combine> $target
for $for
where
Item::Error: $($deps)*,
XMod::Error: $($deps)*,
XEnt::Error: $($deps)*,
$body
};
}
impl_for_conflict!(Clone, (Clone), ConflictKind<Item, XMod, XEnt>, {
fn clone(&self) -> Self {
match self {
ConflictKind::Item(it_e) => ConflictKind::Item(it_e.clone()),
ConflictKind::Module(mod_e) => ConflictKind::Module(mod_e.clone()),
ConflictKind::XEnt(ent_e) => ConflictKind::XEnt(ent_e.clone()),
ConflictKind::ItemModule => ConflictKind::ItemModule,
}
}
});
impl_for_conflict!(fmt::Debug, (fmt::Debug), ConflictKind<Item, XMod, XEnt>, {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConflictKind::Item(it_e) =>
f.debug_tuple("TreeCombineErr::Item").field(it_e).finish(),
ConflictKind::Module(mod_e) =>
f.debug_tuple("TreeCombineErr::Module").field(mod_e).finish(),
ConflictKind::XEnt(ent_e) =>
f.debug_tuple("TreeCombineErr::XEnt").field(ent_e).finish(),
ConflictKind::ItemModule => write!(f, "TreeCombineErr::Item2Module"),
}
}
});
/// Error produced when two trees cannot be merged
pub struct TreeConflict<Item: Combine, XMod: Combine, XEnt: Combine> {
/// Which subtree caused the failure
pub path: VPath,
/// What type of failure occurred
pub kind: ConflictKind<Item, XMod, XEnt>,
}
impl<Item: Combine, XMod: Combine, XEnt: Combine> TreeConflict<Item, XMod, XEnt> {
fn new(kind: ConflictKind<Item, XMod, XEnt>) -> Self { Self { path: VPath::new([]), kind } }
fn push(self, seg: Tok<String>) -> Self {
Self { path: self.path.prefix([seg]), kind: self.kind }
}
}
impl_for_conflict!(Clone, (Clone), TreeConflict<Item, XMod, XEnt>, {
fn clone(&self) -> Self {
Self { path: self.path.clone(), kind: self.kind.clone() }
}
});
impl_for_conflict!(fmt::Debug, (fmt::Debug), TreeConflict<Item, XMod, XEnt>, {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TreeConflict")
.field("path", &self.path)
.field("kind", &self.kind)
.finish()
}
});
impl<Item: Combine, XMod: Combine, XEnt: Combine> Combine for ModMember<Item, XMod, XEnt> {
type Error = TreeConflict<Item, XMod, XEnt>;
fn combine(self, other: Self) -> Result<Self, Self::Error> {
match (self, other) {
(Self::Item(i1), Self::Item(i2)) => match i1.combine(i2) {
Ok(i) => Ok(Self::Item(i)),
Err(e) => Err(TreeConflict::new(ConflictKind::Item(e))),
},
(Self::Sub(m1), Self::Sub(m2)) => m1.combine(m2).map(Self::Sub),
(..) => Err(TreeConflict::new(ConflictKind::ItemModule)),
}
}
}
/// Data about a name in a [Module]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModEntry<Item, XMod, XEnt> {
/// The submodule or item
pub member: ModMember<Item, XMod, XEnt>,
/// Additional fields
pub x: XEnt,
}
impl<Item: Combine, XMod: Combine, XEnt: Combine> Combine for ModEntry<Item, XMod, XEnt> {
type Error = TreeConflict<Item, XMod, XEnt>;
fn combine(self, other: Self) -> Result<Self, Self::Error> {
match self.x.combine(other.x) {
Err(e) => Err(TreeConflict::new(ConflictKind::XEnt(e))),
Ok(x) => Ok(Self { x, member: self.member.combine(other.member)? }),
}
}
}
impl<Item, XMod, XEnt> ModEntry<Item, XMod, XEnt> {
/// Returns the item in this entry if it contains one.
#[must_use]
pub fn item(&self) -> Option<&Item> {
match &self.member {
ModMember::Item(it) => Some(it),
ModMember::Sub(_) => None,
}
}
}
impl<Item, XMod, XEnt> TreeTransforms for ModEntry<Item, XMod, XEnt> {
type Item = Item;
type XEnt = XEnt;
type XMod = XMod;
type SelfType<T, U, V> = ModEntry<T, U, V>;
fn map_data_rec<T, U, V>(
self,
item: &mut impl FnMut(Substack<Tok<String>>, Item) -> T,
module: &mut impl FnMut(Substack<Tok<String>>, XMod) -> U,
entry: &mut impl FnMut(Substack<Tok<String>>, XEnt) -> V,
path: Substack<Tok<String>>,
) -> Self::SelfType<T, U, V> {
ModEntry {
member: self.member.map_data_rec(item, module, entry, path.clone()),
x: entry(path, self.x),
}
}
fn search_rec<'a, T, E>(
&'a self,
state: T,
stack: Substack<Tok<String>>,
callback: &mut impl FnMut(
Substack<Tok<String>>,
ModMemberRef<'a, Item, XMod, XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E> {
self.member.search_rec(state, stack, callback)
}
}
impl<Item, XMod, XEnt: Default> ModEntry<Item, XMod, XEnt> {
/// Wrap a member directly with trivial metadata
pub fn wrap(member: ModMember<Item, XMod, XEnt>) -> Self { Self { member, x: XEnt::default() } }
/// Wrap an item directly with trivial metadata
pub fn leaf(item: Item) -> Self { Self::wrap(ModMember::Item(item)) }
}
impl<Item, XMod: Default, XEnt: Default> ModEntry<Item, XMod, XEnt> {
/// Create an empty submodule
pub fn empty() -> Self { Self::wrap(ModMember::Sub(Module::wrap([]))) }
/// Create a module
#[must_use]
pub fn tree<K: AsRef<str>>(arr: impl IntoIterator<Item = (K, Self)>) -> Self {
Self::wrap(ModMember::Sub(Module::wrap(arr.into_iter().map(|(k, v)| (intern(k.as_ref()), v)))))
}
/// Create a record in the list passed to [ModEntry#tree] which describes a
/// submodule. This mostly exists to deal with strange rustfmt block
/// breaking behaviour
pub fn tree_ent<K: AsRef<str>>(key: K, arr: impl IntoIterator<Item = (K, Self)>) -> (K, Self) {
(key, Self::tree(arr))
}
/// Namespace the tree with the list of names
///
/// The unarray is used to trick rustfmt into breaking the sub-item
/// into a block without breaking anything else.
#[must_use]
pub fn ns(name: impl AsRef<str>, [mut end]: [Self; 1]) -> Self {
let elements = name.as_ref().split("::").collect::<Vec<_>>();
for name in elements.into_iter().rev() {
end = Self::tree([(name, end)]);
}
end
}
fn not_mod_panic<T>() -> T { panic!("Expected module but found leaf") }
/// Return the wrapped module. Panic if the entry wraps an item
pub fn unwrap_mod(self) -> Module<Item, XMod, XEnt> {
if let ModMember::Sub(m) = self.member { m } else { Self::not_mod_panic() }
}
/// Return the wrapped module. Panic if the entry wraps an item
pub fn unwrap_mod_ref(&self) -> &Module<Item, XMod, XEnt> {
if let ModMember::Sub(m) = &self.member { m } else { Self::not_mod_panic() }
}
}
/// A module, containing imports,
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Module<Item, XMod, XEnt> {
/// Submodules and items by name
pub entries: HashMap<Tok<String>, ModEntry<Item, XMod, XEnt>>,
/// Additional fields
pub x: XMod,
}
use crate::api;
use crate::error::OrcErr;
use crate::interner::{deintern, intern, Tok};
use crate::name::{NameLike, VName};
use crate::tokens::{OwnedPh, PARENS};
trait_set! {
/// A filter applied to a module tree
pub trait Filter<'a, Item, XMod, XEnt> =
for<'b> Fn(&'b ModEntry<Item, XMod, XEnt>) -> bool + Clone + 'a
pub trait RecurCB<'a, A: AtomInTok, X> = Fn(TokTree<'a, A, X>) -> TokTree<'a, A, X>;
}
/// A line in a [Module]
pub type Record<Item, XMod, XEnt> = (Tok<String>, ModEntry<Item, XMod, XEnt>);
impl<Item, XMod, XEnt> Module<Item, XMod, XEnt> {
/// Returns child names for which the value matches a filter
#[must_use]
pub fn keys<'a>(
&'a self,
filter: impl for<'b> Fn(&'b ModEntry<Item, XMod, XEnt>) -> bool + 'a,
) -> BoxedIter<Tok<String>> {
Box::new((self.entries.iter()).filter(move |(_, v)| filter(v)).map(|(k, _)| k.clone()))
}
/// Return the module at the end of the given path
pub fn walk_ref<'a: 'b, 'b>(
&'a self,
prefix: &'b [Tok<String>],
path: &'b [Tok<String>],
filter: impl Filter<'b, Item, XMod, XEnt>,
) -> Result<&'a Self, WalkError<'b>> {
let mut module = self;
for (pos, step) in path.iter().enumerate() {
let kind = match module.entries.get(step) {
None => ErrKind::Missing,
Some(ent) if !filter(ent) => ErrKind::Filtered,
Some(ModEntry { member: ModMember::Item(_), .. }) => ErrKind::NotModule,
Some(ModEntry { member: ModMember::Sub(next), .. }) => {
module = next;
continue;
},
};
let options = Sequence::new(move || module.keys(filter.clone()));
return Err(WalkError { kind, prefix, path, pos, options });
}
Ok(module)
}
/// Return the member at the end of the given path
///
/// # Panics
///
/// if path is empty, since the reference cannot be forwarded that way
pub fn walk1_ref<'a: 'b, 'b>(
&'a self,
prefix: &'b [Tok<String>],
path: &'b [Tok<String>],
filter: impl Filter<'b, Item, XMod, XEnt>,
) -> Result<(&'a ModEntry<Item, XMod, XEnt>, &'a Self), WalkError<'b>> {
let (last, parent) = path.split_last().expect("Path cannot be empty");
let pos = path.len() - 1;
let module = self.walk_ref(prefix, parent, filter.clone())?;
let err_kind = match &module.entries.get(last) {
Some(entry) if filter(entry) => return Ok((entry, module)),
Some(_) => ErrKind::Filtered,
None => ErrKind::Missing,
pub fn recur<'a, A: AtomInTok, X>(
tt: TokTree<'a, A, X>,
f: &impl Fn(TokTree<'a, A, X>, &dyn RecurCB<'a, A, X>) -> TokTree<'a, A, X>,
) -> TokTree<'a, A, X> {
f(tt, &|TokTree { range, tok }| {
let tok = match tok {
tok @ (Token::Atom(_) | Token::BR | Token::Bottom(_) | Token::Comment(_) | Token::NS) => tok,
tok @ (Token::Name(_) | Token::Ph(_) | Token::Slot(_) | Token::X(_)) => tok,
Token::LambdaHead(arg) =>
Token::LambdaHead(arg.into_iter().map(|tt| recur(tt, f)).collect_vec()),
Token::S(p, b) => Token::S(p, b.into_iter().map(|tt| recur(tt, f)).collect_vec()),
};
let options = Sequence::new(move || module.keys(filter.clone()));
Err(WalkError { kind: err_kind, options, prefix, path, pos })
TokTree { range, tok }
})
}
pub trait AtomInTok: Display + Clone {
type Context: ?Sized;
fn from_api(atom: &api::Atom, pos: Range<u32>, ctx: &mut Self::Context) -> Self;
fn to_api(&self) -> api::Atom;
}
impl AtomInTok for Never {
type Context = Never;
fn from_api(_: &api::Atom, _: Range<u32>, _: &mut Self::Context) -> Self { panic!() }
fn to_api(&self) -> orchid_api::Atom { match *self {} }
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct TreeHandle<'a>(api::TreeTicket, PhantomData<&'a ()>);
impl TreeHandle<'static> {
pub fn new(tt: api::TreeTicket) -> Self { TreeHandle(tt, PhantomData) }
}
impl<'a> TreeHandle<'a> {
pub fn ticket(self) -> api::TreeTicket { self.0 }
}
impl<'a> Display for TreeHandle<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Handle({})", self.0.0) }
}
#[derive(Clone, Debug)]
pub struct TokTree<'a, A: AtomInTok, X> {
pub tok: Token<'a, A, X>,
pub range: Range<u32>,
}
impl<'a, A: AtomInTok, X> TokTree<'a, A, X> {
pub fn from_api(tt: &api::TokenTree, ctx: &mut A::Context) -> Self {
let tok = match &tt.token {
api::Token::Atom(a) => Token::Atom(A::from_api(a, tt.range.clone(), ctx)),
api::Token::BR => Token::BR,
api::Token::NS => Token::NS,
api::Token::Bottom(e) => Token::Bottom(e.iter().map(OrcErr::from_api).collect()),
api::Token::Lambda(arg) => Token::LambdaHead(ttv_from_api(arg, ctx)),
api::Token::Name(name) => Token::Name(deintern(*name)),
api::Token::Ph(ph) => Token::Ph(OwnedPh::from_api(ph.clone())),
api::Token::S(par, b) => Token::S(par.clone(), ttv_from_api(b, ctx)),
api::Token::Comment(c) => Token::Comment(c.clone()),
api::Token::Slot(id) => Token::Slot(TreeHandle::new(*id)),
};
Self { range: tt.range.clone(), tok }
}
/// Walk from one node to another in a tree, asserting that the origin can see
/// the target.
///
/// # Panics
///
/// If the target is the root node
pub fn inner_walk<'a: 'b, 'b>(
&'a self,
origin: &[Tok<String>],
target: &'b [Tok<String>],
is_exported: impl for<'c> Fn(&'c ModEntry<Item, XMod, XEnt>) -> bool + Clone + 'b,
) -> Result<(&'a ModEntry<Item, XMod, XEnt>, &'a Self), WalkError<'b>> {
let ignore_vis_len = 1 + origin.iter().zip(target).take_while(|(a, b)| a == b).count();
if target.len() <= ignore_vis_len {
return self.walk1_ref(&[], target, |_| true);
pub fn to_api(
&self,
do_extra: &mut impl FnMut(&X, Range<u32>) -> api::TokenTree,
) -> api::TokenTree {
let token = match &self.tok {
Token::Atom(a) => api::Token::Atom(a.to_api()),
Token::BR => api::Token::BR,
Token::NS => api::Token::NS,
Token::Bottom(e) => api::Token::Bottom(e.iter().map(OrcErr::to_api).collect()),
Token::Comment(c) => api::Token::Comment(c.clone()),
Token::LambdaHead(arg) =>
api::Token::Lambda(arg.iter().map(|t| t.to_api(do_extra)).collect_vec()),
Token::Name(n) => api::Token::Name(n.marker()),
Token::Ph(ph) => api::Token::Ph(ph.to_api()),
Token::Slot(tt) => api::Token::Slot(tt.ticket()),
Token::S(p, b) => api::Token::S(p.clone(), b.iter().map(|t| t.to_api(do_extra)).collect()),
Token::X(x) => return do_extra(x, self.range.clone()),
};
api::TokenTree { range: self.range.clone(), token }
}
}
impl<'a, A: AtomInTok + Display, X: Display> Display for TokTree<'a, A, X> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.tok) }
}
pub fn ttv_from_api<A: AtomInTok, X>(
tokv: impl IntoIterator<Item: Borrow<api::TokenTree>>,
ctx: &mut A::Context,
) -> Vec<TokTree<'static, A, X>> {
tokv.into_iter().map(|t| TokTree::<A, X>::from_api(t.borrow(), ctx)).collect()
}
pub fn ttv_to_api<'a, A: AtomInTok, X>(
tokv: impl IntoIterator<Item: Borrow<TokTree<'a, A, X>>>,
do_extra: &mut impl FnMut(&X, Range<u32>) -> api::TokenTree,
) -> Vec<api::TokenTree> {
tokv
.into_iter()
.map(|tok| {
let tt: &TokTree<A, X> = tok.borrow();
tt.to_api(do_extra)
})
.collect_vec()
}
pub fn vname_tv<'a: 'b, 'b, A: AtomInTok + 'a, X: 'a>(
name: &'b VName,
ran: Range<u32>,
) -> impl Iterator<Item = TokTree<'a, A, X>> + 'b {
let (head, tail) = name.split_first();
iter::once(Token::Name(head))
.chain(tail.iter().flat_map(|t| [Token::NS, Token::Name(t)]))
.map(move |t| t.at(ran.clone()))
}
pub fn wrap_tokv<'a, A: AtomInTok + 'a, X: 'a>(
items: Vec<TokTree<'a, A, X>>,
range: Range<u32>,
) -> TokTree<'a, A, X> {
match items.len() {
1 => items.into_iter().next().unwrap(),
_ => Token::S(api::Paren::Round, items).at(range),
}
}
pub fn ph(s: &str) -> OwnedPh {
match s.strip_prefix("..") {
Some(v_tail) => {
let (mid, priority) = match v_tail.split_once(':') {
Some((h, t)) => (h, t.parse().expect("priority not an u8")),
None => (v_tail, 0),
};
let (name, nonzero) = match mid.strip_prefix(".$") {
Some(name) => (name, true),
None => (mid.strip_prefix('$').expect("Invalid placeholder"), false),
};
if name.starts_with("_") {
panic!("Names starting with an underscore indicate a single-name scalar placeholder")
}
OwnedPh {
name: intern(name),
kind: api::PlaceholderKind::Vector { nz: nonzero, prio: priority },
}
},
None => match s.strip_prefix("$_") {
Some(name) => OwnedPh { name: intern(name), kind: api::PlaceholderKind::Name },
None => match s.strip_prefix("$") {
None => panic!("Invalid placeholder"),
Some(name) => OwnedPh { name: intern(name), kind: api::PlaceholderKind::Scalar },
},
},
}
}
pub use api::Paren;
#[derive(Clone, Debug)]
pub enum Token<'a, A: AtomInTok, X> {
Comment(Arc<String>),
LambdaHead(Vec<TokTree<'a, A, X>>),
Name(Tok<String>),
NS,
BR,
S(Paren, Vec<TokTree<'a, A, X>>),
Atom(A),
Ph(OwnedPh),
Bottom(Vec<OrcErr>),
Slot(TreeHandle<'a>),
X(X),
}
impl<'a, A: AtomInTok, X> Token<'a, A, X> {
pub fn at(self, range: Range<u32>) -> TokTree<'a, A, X> { TokTree { range, tok: self } }
}
impl<'a, A: AtomInTok + Display, X: Display> Display for Token<'a, A, X> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
thread_local! {
static PAREN_LEVEL: RefCell<usize> = 0.into();
}
let (ignore_vis_path, hidden_path) = target.split_at(ignore_vis_len);
let first_divergence = self.walk_ref(&[], ignore_vis_path, |_| true)?;
first_divergence.walk1_ref(ignore_vis_path, hidden_path, is_exported)
}
/// Wrap entry table in a module with trivial metadata
pub fn wrap(entries: impl IntoIterator<Item = Record<Item, XMod, XEnt>>) -> Self
where XMod: Default {
Self { entries: entries.into_iter().collect(), x: XMod::default() }
}
}
impl<Item, XMod, XEnt> TreeTransforms for Module<Item, XMod, XEnt> {
type Item = Item;
type XEnt = XEnt;
type XMod = XMod;
type SelfType<T, U, V> = Module<T, U, V>;
fn map_data_rec<T, U, V>(
self,
item: &mut impl FnMut(Substack<Tok<String>>, Item) -> T,
module: &mut impl FnMut(Substack<Tok<String>>, XMod) -> U,
entry: &mut impl FnMut(Substack<Tok<String>>, XEnt) -> V,
path: Substack<Tok<String>>,
) -> Self::SelfType<T, U, V> {
Module {
x: module(path.clone(), self.x),
entries: (self.entries.into_iter())
.map(|(k, e)| (k.clone(), e.map_data_rec(item, module, entry, path.push(k))))
.collect(),
fn get_indent() -> usize { PAREN_LEVEL.with_borrow(|t| *t) }
fn with_indent<T>(f: impl FnOnce() -> T) -> T {
PAREN_LEVEL.with_borrow_mut(|t| *t += 1);
let r = f();
PAREN_LEVEL.with_borrow_mut(|t| *t -= 1);
r
}
}
fn search_rec<'a, T, E>(
&'a self,
mut state: T,
stack: Substack<Tok<String>>,
callback: &mut impl FnMut(
Substack<Tok<String>>,
ModMemberRef<'a, Item, XMod, XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E> {
state = callback(stack.clone(), ModMemberRef::Mod(self), state)?;
for (key, value) in &self.entries {
state = value.search_rec(state, stack.push(key.clone()), callback)?;
}
Ok(state)
}
}
impl<Item: Combine, XMod: Combine, XEnt: Combine> Combine for Module<Item, XMod, XEnt> {
type Error = TreeConflict<Item, XMod, XEnt>;
fn combine(self, Self { entries, x }: Self) -> Result<Self, Self::Error> {
let entries =
try_join_maps(self.entries, entries, |k, l, r| l.combine(r).map_err(|e| e.push(k.clone())))?;
let x = (self.x.combine(x)).map_err(|e| TreeConflict::new(ConflictKind::Module(e)))?;
Ok(Self { x, entries })
}
}
impl<Item: fmt::Display, TExt: fmt::Display, XEnt: fmt::Display> fmt::Display
for Module<Item, TExt, XEnt>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "module {{")?;
for (name, ModEntry { member, x: extra }) in &self.entries {
match member {
ModMember::Sub(module) => write!(f, "\n{name} {extra} = {module}"),
ModMember::Item(item) => write!(f, "\n{name} {extra} = {item}"),
}?;
}
write!(f, "\n\n{}\n}}", &self.x)
}
}
/// A non-owning version of [ModMember]. Either an item-ref or a module-ref.
pub enum ModMemberRef<'a, Item, XMod, XEnt> {
/// Leaf
Item(&'a Item),
/// Node
Mod(&'a Module<Item, XMod, XEnt>),
}
/// Possible causes why the path could not be walked
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ErrKind {
/// `require_exported` was set to `true` and a module wasn't exported
Filtered,
/// A module was not found
Missing,
/// The path leads into a leaf node
NotModule,
}
impl ErrKind {
pub const fn msg(&self) -> &'static str {
match self {
Self::Filtered => "The path leads into a private module",
Self::Missing => "Nonexistent path",
Self::NotModule => "The path leads into a leaf",
Self::Atom(a) => f.write_str(&indent(&format!("{a}"), get_indent(), false)),
Self::BR => write!(f, "\n{}", " ".repeat(get_indent())),
Self::Bottom(err) => write!(
f,
"Botttom({})",
err.iter().map(|e| format!("{}: {}", e.description, e.message)).join(", ")
),
Self::Comment(c) => write!(f, "--[{c}]--"),
Self::LambdaHead(arg) => with_indent(|| write!(f, "\\ {} .", ttv_fmt(arg))),
Self::NS => f.write_str("::"),
Self::Name(n) => f.write_str(n),
Self::Ph(ph) => write!(f, "{ph}"),
Self::Slot(th) => write!(f, "{th}"),
Self::S(p, b) => {
let (lp, rp, _) = PARENS.iter().find(|(_, _, par)| par == p).unwrap();
f.write_char(*lp)?;
with_indent(|| f.write_str(&ttv_fmt(b)))?;
f.write_char(*rp)
},
Self::X(x) => write!(f, "{x}"),
}
}
}
#[derive(Clone)]
/// All details about a failed tree-walk
pub struct WalkError<'a> {
/// Failure mode
kind: ErrKind,
/// Path to the module where the walk started
prefix: &'a [Tok<String>],
/// Planned walk path
path: &'a [Tok<String>],
/// Index into walked path where the error occurred
pos: usize,
/// Alternatives to the failed steps
options: Sequence<'a, Tok<String>>,
pub fn ttv_fmt<'a>(
ttv: impl IntoIterator<Item = &'a TokTree<'a, impl AtomInTok + 'a, impl Display + 'a>>,
) -> String {
ttv.into_iter().join(" ")
}
impl<'a> WalkError<'a> {
/// Total length of the path represented by this error
#[must_use]
pub fn depth(&self) -> usize { self.prefix.len() + self.pos + 1 }
pub fn alternatives(&self) -> BoxedIter<Tok<String>> { self.options.iter() }
/// Get the total path including the step that caused the error
pub fn full_path(&self) -> VName {
VName::new((self.prefix.iter()).chain(self.path.iter().take(self.pos + 1)).cloned())
.expect("empty paths don't cause an error")
}
/// Construct an error for the very last item in a slice. This is often done
/// outside [super::tree] so it gets a function rather than exposing the
/// fields of [WalkError]
pub fn last(path: &'a [Tok<String>], kind: ErrKind, options: Sequence<'a, Tok<String>>) -> Self {
WalkError { kind, path, options, pos: path.len() - 1, prefix: &[] }
pub fn indent(s: &str, lvl: usize, first: bool) -> String {
if first {
s.replace("\n", &("\n".to_string() + &" ".repeat(lvl)))
} else if let Some((fst, rest)) = s.split_once('\n') {
fst.to_string() + "\n" + &indent(rest, lvl, true)
} else {
s.to_string()
}
}
impl<'a> fmt::Debug for WalkError<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WalkError")
.field("kind", &self.kind)
.field("prefix", &self.prefix)
.field("path", &self.path)
.field("pos", &self.pos)
.finish_non_exhaustive()
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_covariance() {
fn _f<'a>(x: Token<'static, Never, ()>) -> Token<'a, Never, ()> { x }
}
#[test]
fn fail_covariance() {
// this fails to compile
// fn _f<'a, 'b>(x: &'a mut &'static ()) -> &'a mut &'b () { x }
// this passes because it's covariant
fn _f<'a, 'b>(x: &'a fn() -> &'static ()) -> &'a fn() -> &'b () { x }
}
}