forked from Orchid/orchid
partway towards commands
I got very confused and started mucking about with "spawn" when in fact all I needed was the "inline" extension type in orcx that allows the interpreter to expose custom constants.
This commit is contained in:
@@ -8,6 +8,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
|
||||
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
|
||||
orchid-async-utils = { version = "0.1.0", path = "../orchid-async-utils" }
|
||||
async-once-cell = "0.5.4"
|
||||
bound = "0.6.0"
|
||||
derive_destructure = "1.0.0"
|
||||
@@ -33,3 +34,5 @@ task-local = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
futures = "0.3.31"
|
||||
rand = "0.10.0"
|
||||
rand_chacha = "0.10.0"
|
||||
|
||||
@@ -54,6 +54,7 @@ static BORROWED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|
||||
|
||||
/// Convert a future to a binary-compatible format that can be sent across
|
||||
/// dynamic library boundaries
|
||||
#[must_use]
|
||||
pub fn future_to_vt<Fut: Future<Output = ()> + 'static>(fut: Fut) -> api::binary::FutureBin {
|
||||
let wide_box = Box::new(fut) as WideBox;
|
||||
let data = Box::into_raw(Box::new(wide_box));
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub enum ArcCow<'a, T: ?Sized + ToOwned> {
|
||||
Borrowed(&'a T),
|
||||
Owned(Arc<T::Owned>),
|
||||
}
|
||||
impl<T: ?Sized + ToOwned> ArcCow<'_, T> {
|
||||
pub fn owned(value: T::Owned) -> Self { Self::Owned(Arc::new(value)) }
|
||||
}
|
||||
impl<T: ?Sized + ToOwned> Clone for ArcCow<'_, T> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Borrowed(r) => Self::Borrowed(r),
|
||||
Self::Owned(b) => Self::Owned(b.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized + ToOwned> Deref for ArcCow<'_, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Borrowed(t) => t,
|
||||
Self::Owned(b) => b.as_ref().borrow(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,20 +3,22 @@
|
||||
use std::iter;
|
||||
|
||||
/// A trait object of [Iterator] to be assigned to variables that may be
|
||||
/// initialized form multiple iterators of incompatible types
|
||||
/// initialized form iterators of multiple or unknown types
|
||||
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
|
||||
/// creates a [BoxedIter] of a single element
|
||||
#[must_use]
|
||||
pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> { Box::new(iter::once(t)) }
|
||||
/// creates an empty [BoxedIter]
|
||||
#[must_use]
|
||||
pub fn box_empty<'a, T: 'a>() -> BoxedIter<'a, T> { Box::new(iter::empty()) }
|
||||
|
||||
/// Chain various iterators into a [BoxedIter]
|
||||
#[macro_export]
|
||||
macro_rules! box_chain {
|
||||
($curr:expr) => {
|
||||
Box::new($curr) as $crate::boxed_iter::BoxedIter<_>
|
||||
Box::new($curr) as $crate::BoxedIter<_>
|
||||
};
|
||||
($curr:expr, $($rest:expr),*) => {
|
||||
Box::new($curr$(.chain($rest))*) as $crate::boxed_iter::BoxedIter<_>
|
||||
Box::new($curr$(.chain($rest))*) as $crate::BoxedIter<_>
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ use itertools::Itertools;
|
||||
|
||||
use crate::api;
|
||||
|
||||
pub type CRange = RangeInclusive<char>;
|
||||
|
||||
/// A fast character filter to avoid superfluous extension calls in the lexer.
|
||||
pub trait ICFilter: fmt::Debug {
|
||||
/// Returns an ordered set of character ranges
|
||||
fn ranges(&self) -> &[RangeInclusive<char>];
|
||||
}
|
||||
impl ICFilter for [RangeInclusive<char>] {
|
||||
@@ -17,7 +17,10 @@ impl ICFilter for api::CharFilter {
|
||||
fn ranges(&self) -> &[RangeInclusive<char>] { &self.0 }
|
||||
}
|
||||
|
||||
fn try_merge_char_ranges(left: CRange, right: CRange) -> Result<CRange, (CRange, CRange)> {
|
||||
fn try_merge_char_ranges(
|
||||
left: RangeInclusive<char>,
|
||||
right: RangeInclusive<char>,
|
||||
) -> Result<RangeInclusive<char>, (RangeInclusive<char>, RangeInclusive<char>)> {
|
||||
match *left.end() as u32 + 1 < *right.start() as u32 {
|
||||
true => Err((left, right)),
|
||||
false => Ok(*left.start()..=*right.end()),
|
||||
@@ -25,8 +28,9 @@ 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>) -> api::CharFilter {
|
||||
/// requirements of [api::CharFilter]
|
||||
#[must_use]
|
||||
pub fn mk_char_filter(items: impl IntoIterator<Item = RangeInclusive<char>>) -> api::CharFilter {
|
||||
api::CharFilter(
|
||||
(items.into_iter())
|
||||
.filter(|r| *r.start() as u32 <= *r.end() as u32)
|
||||
@@ -37,6 +41,7 @@ pub fn mk_char_filter(items: impl IntoIterator<Item = CRange>) -> api::CharFilte
|
||||
}
|
||||
|
||||
/// Decide whether a char filter matches a character via binary search
|
||||
#[must_use]
|
||||
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
|
||||
@@ -48,6 +53,7 @@ pub fn char_filter_match(cf: &(impl ICFilter + ?Sized), c: char) -> bool {
|
||||
|
||||
/// Merge two char filters into a filter that matches if either of the
|
||||
/// constituents would match.
|
||||
#[must_use]
|
||||
pub fn char_filter_union(
|
||||
l: &(impl ICFilter + ?Sized),
|
||||
r: &(impl ICFilter + ?Sized),
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
//! The concept of a fallible merger
|
||||
|
||||
use never::Never;
|
||||
|
||||
/// Fallible, type-preserving variant of [std::ops::Add] implemented by a
|
||||
/// variety of types for different purposes. Very broadly, if the operation
|
||||
/// succeeds, the result should represent _both_ inputs.
|
||||
pub trait Combine: Sized {
|
||||
/// Information about the failure
|
||||
type Error;
|
||||
|
||||
/// Merge two values into a value that represents both, if this is possible.
|
||||
fn combine(self, other: Self) -> Result<Self, Self::Error>;
|
||||
}
|
||||
|
||||
impl Combine for Never {
|
||||
type Error = Never;
|
||||
fn combine(self, _: Self) -> Result<Self, Self::Error> { match self {} }
|
||||
}
|
||||
|
||||
impl Combine for () {
|
||||
type Error = Never;
|
||||
fn combine(self, (): Self) -> Result<Self, Self::Error> { Ok(()) }
|
||||
}
|
||||
@@ -16,9 +16,8 @@ use futures::{
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use orchid_api_traits::{Decode, Encode, Request, UnderRoot};
|
||||
|
||||
use crate::future_debug::{PanicOnDrop, assert_no_drop};
|
||||
use crate::localset::LocalSet;
|
||||
use orchid_async_utils::LocalSet;
|
||||
use orchid_async_utils::debug::{PanicOnDrop, assert_no_drop};
|
||||
|
||||
#[must_use = "Receipts indicate that a required action has been performed within a function. \
|
||||
Most likely this should be returned somewhere."]
|
||||
@@ -445,10 +444,10 @@ mod test {
|
||||
use futures::{SinkExt, StreamExt, join};
|
||||
use orchid_api_derive::{Coding, Hierarchy};
|
||||
use orchid_api_traits::Request;
|
||||
use orchid_async_utils::debug::spin_on;
|
||||
use unsync_pipe::pipe;
|
||||
|
||||
use crate::future_debug::spin_on;
|
||||
use crate::reqnot::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm};
|
||||
use crate::comm::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Coding, Hierarchy)]
|
||||
#[extendable]
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::num::{NonZero, NonZeroUsize};
|
||||
use std::ops::Add;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@@ -10,9 +11,7 @@ use futures::future::join_all;
|
||||
use itertools::Itertools;
|
||||
use task_local::task_local;
|
||||
|
||||
use crate::api;
|
||||
use crate::interner::{IStr, es, is};
|
||||
use crate::location::Pos;
|
||||
use crate::{IStr, Pos, api, es, is};
|
||||
|
||||
/// A point of interest in resolving the error, such as the point where
|
||||
/// processing got stuck, a command that is likely to be incorrect
|
||||
@@ -24,12 +23,15 @@ pub struct ErrPos {
|
||||
pub message: Option<Arc<String>>,
|
||||
}
|
||||
impl ErrPos {
|
||||
/// Create from a position with a position-specific message. If there's no
|
||||
/// message, use `Pos::into`
|
||||
#[must_use]
|
||||
pub fn new(msg: &str, position: Pos) -> Self {
|
||||
Self { message: Some(Arc::new(msg.to_string())), position }
|
||||
}
|
||||
async fn from_api(api: &api::ErrLocation) -> Self {
|
||||
async fn from_api(api: api::ErrLocation) -> Self {
|
||||
Self {
|
||||
message: Some(api.message.clone()).filter(|s| !s.is_empty()),
|
||||
message: Some(api.message).filter(|s| !s.is_empty()),
|
||||
position: Pos::from_api(&api.location).await,
|
||||
}
|
||||
}
|
||||
@@ -52,10 +54,16 @@ impl fmt::Display for ErrPos {
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that occurred in Orchid code, whether at startup or runtime
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OrcErr {
|
||||
/// A generic error message used in categorizing errors
|
||||
/// You can also equality-compare atoms with these message tokens
|
||||
pub description: IStr,
|
||||
pub message: Arc<String>,
|
||||
/// A specific error message that may include values relevant in resolving the
|
||||
/// error
|
||||
pub message: Rc<String>,
|
||||
/// Various locations in code that may be useful in resolving the error
|
||||
pub positions: Vec<ErrPos>,
|
||||
}
|
||||
impl OrcErr {
|
||||
@@ -66,11 +74,11 @@ impl OrcErr {
|
||||
locations: self.positions.iter().map(ErrPos::to_api).collect(),
|
||||
}
|
||||
}
|
||||
async fn from_api(api: &api::OrcError) -> Self {
|
||||
async fn from_api(api: api::OrcError) -> Self {
|
||||
Self {
|
||||
description: es(api.description).await,
|
||||
message: api.message.clone(),
|
||||
positions: join_all(api.locations.iter().map(ErrPos::from_api)).await,
|
||||
message: api.message,
|
||||
positions: join_all(api.locations.into_iter().map(ErrPos::from_api)).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,6 +95,8 @@ impl fmt::Display for OrcErr {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rust error produced when an Orchid error condition arises but no
|
||||
/// specific errors are listed. This is always a Rust programmer error.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EmptyErrv;
|
||||
impl fmt::Display for EmptyErrv {
|
||||
@@ -95,39 +105,57 @@ impl fmt::Display for EmptyErrv {
|
||||
}
|
||||
}
|
||||
|
||||
/// A container for one or more errors. Code that supports error recovery should
|
||||
/// use these instead of plain [OrcErr] objects.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OrcErrv(Vec<OrcErr>);
|
||||
impl OrcErrv {
|
||||
/// Create from individual errors. If you have exactly one initial error, see
|
||||
/// [mk_errv]
|
||||
pub fn new(errors: impl IntoIterator<Item = OrcErr>) -> Result<Self, EmptyErrv> {
|
||||
let v = errors.into_iter().collect_vec();
|
||||
if v.is_empty() { Err(EmptyErrv) } else { Ok(Self(v)) }
|
||||
}
|
||||
/// Add additional errors to this container. Since `OrcErrv` also implements
|
||||
/// [IntoIterator], this can take `(Self, Self)`
|
||||
#[must_use]
|
||||
pub fn extended<T>(mut self, errors: impl IntoIterator<Item = T>) -> Self
|
||||
where Self: Extend<T> {
|
||||
self.extend(errors);
|
||||
self
|
||||
}
|
||||
/// Determine how many distinct errors there are in the container
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize { self.0.len() }
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool { self.len() == 0 }
|
||||
pub fn len(&self) -> NonZeroUsize { NonZero::new(self.0.len()).expect("OrcErrv cannot be empty") }
|
||||
/// See if any errors match a particular filter criteria. This is useful for
|
||||
/// sentinel errors whch are produced by user code to trigger unique
|
||||
/// behaviours such as a lexer mismatch
|
||||
#[must_use]
|
||||
pub fn any(&self, f: impl FnMut(&OrcErr) -> bool) -> bool { self.0.iter().any(f) }
|
||||
/// Remove all errors that don't match a filter criterion. If no errors match,
|
||||
/// nothing is returned
|
||||
#[must_use]
|
||||
pub fn keep_only(self, f: impl FnMut(&OrcErr) -> bool) -> Option<Self> {
|
||||
let v = self.0.into_iter().filter(f).collect_vec();
|
||||
if v.is_empty() { None } else { Some(Self(v)) }
|
||||
}
|
||||
/// If there is exactly one error, return it. Mostly used for simplified
|
||||
/// printing
|
||||
#[must_use]
|
||||
pub fn one(&self) -> Option<&OrcErr> { (self.0.len() == 1).then(|| &self.0[9]) }
|
||||
/// Iterate over all positions of all errors
|
||||
pub fn pos_iter(&self) -> impl Iterator<Item = ErrPos> + '_ {
|
||||
self.0.iter().flat_map(|e| e.positions.iter().cloned())
|
||||
}
|
||||
/// Serialize for transmission
|
||||
#[must_use]
|
||||
pub fn to_api(&self) -> Vec<api::OrcError> { self.0.iter().map(OrcErr::to_api).collect() }
|
||||
pub async fn from_api<'a>(api: impl IntoIterator<Item = &'a api::OrcError>) -> Self {
|
||||
/// Deserialize from transmission
|
||||
#[must_use]
|
||||
pub async fn from_api(api: impl IntoIterator<Item = api::OrcError>) -> Self {
|
||||
Self(join_all(api.into_iter().map(OrcErr::from_api)).await)
|
||||
}
|
||||
/// Iterate over the errors without consuming the collection
|
||||
pub fn iter(&self) -> impl Iterator<Item = OrcErr> + '_ { self.0.iter().cloned() }
|
||||
}
|
||||
impl From<OrcErr> for OrcErrv {
|
||||
@@ -156,8 +184,11 @@ impl fmt::Display for OrcErrv {
|
||||
}
|
||||
}
|
||||
|
||||
/// A result from a function that may return multiple errors.
|
||||
pub type OrcRes<T> = Result<T, OrcErrv>;
|
||||
|
||||
/// If two fallible values both succeed return both values, otherwise return
|
||||
/// all errors.
|
||||
pub fn join_ok<T, U>(left: OrcRes<T>, right: OrcRes<U>) -> OrcRes<(T, U)> {
|
||||
match (left, right) {
|
||||
(Ok(t), Ok(u)) => Ok((t, u)),
|
||||
@@ -187,15 +218,22 @@ macro_rules! join_ok {
|
||||
};
|
||||
(@TYPES) => { () };
|
||||
(@VALUES $name:ident $(: $ty:ty)? = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => {
|
||||
$crate::error::join_ok($val, $crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*))
|
||||
$crate::join_ok($val, $crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*))
|
||||
};
|
||||
(@VALUES) => { Ok(()) };
|
||||
}
|
||||
|
||||
/// Create an errv without an associated position, as opposed to [mk_errv].
|
||||
/// While this is technically legal and sometimes needed in library code, all
|
||||
/// errors that are technically possible to associate with at least one position
|
||||
/// should be.
|
||||
#[must_use]
|
||||
pub fn mk_errv_floating(description: IStr, message: impl AsRef<str>) -> OrcErrv {
|
||||
mk_errv::<Pos>(description, message, [])
|
||||
}
|
||||
|
||||
/// Create an errv. The third argument can be an iterable of [ErrPos] or [Pos].
|
||||
#[must_use]
|
||||
pub fn mk_errv<I: Into<ErrPos>>(
|
||||
description: IStr,
|
||||
message: impl AsRef<str>,
|
||||
@@ -203,12 +241,14 @@ pub fn mk_errv<I: Into<ErrPos>>(
|
||||
) -> OrcErrv {
|
||||
OrcErr {
|
||||
description,
|
||||
message: Arc::new(message.as_ref().to_string()),
|
||||
message: Rc::new(message.as_ref().to_string()),
|
||||
positions: posv.into_iter().map_into().collect(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Convert a standard IO error into an Orchid error
|
||||
#[must_use]
|
||||
pub async fn async_io_err<I: Into<ErrPos>>(
|
||||
err: std::io::Error,
|
||||
posv: impl IntoIterator<Item = I>,
|
||||
@@ -216,6 +256,8 @@ pub async fn async_io_err<I: Into<ErrPos>>(
|
||||
mk_errv(is(&err.kind().to_string()).await, err.to_string(), posv)
|
||||
}
|
||||
|
||||
/// Decode an Unicode string, or produce a common error related to Unicode
|
||||
/// decoding
|
||||
pub async fn os_str_to_string<I: Into<ErrPos>>(
|
||||
str: &OsStr,
|
||||
posv: impl IntoIterator<Item = I>,
|
||||
@@ -262,6 +304,9 @@ pub async fn try_with_reporter<T>(fut: impl Future<Output = OrcRes<T>>) -> OrcRe
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if there are pending errors or if this overarching procedure has a
|
||||
/// chance to succeed
|
||||
#[must_use]
|
||||
pub async fn is_erroring() -> bool {
|
||||
(REPORTER.try_with(|r| !r.errors.borrow().is_empty()))
|
||||
.expect("Sidechannel errors must be caught by a reporter")
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
//! Multiple-listener-single-delivery event system.
|
||||
|
||||
use std::mem;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::mpsc::{self, sync_channel};
|
||||
|
||||
struct Reply<T, U> {
|
||||
resub: bool,
|
||||
outcome: Result<U, T>,
|
||||
}
|
||||
|
||||
struct Listener<T, E> {
|
||||
sink: mpsc::SyncSender<T>,
|
||||
source: mpsc::Receiver<Reply<T, E>>,
|
||||
}
|
||||
|
||||
pub struct Event<T, U> {
|
||||
listeners: Mutex<Vec<Listener<T, U>>>,
|
||||
}
|
||||
impl<T, U> Event<T, U> {
|
||||
pub const fn new() -> Self { Self { listeners: Mutex::new(Vec::new()) } }
|
||||
|
||||
pub fn dispatch(&self, mut ev: T) -> Option<U> {
|
||||
let mut listeners = self.listeners.lock().unwrap();
|
||||
let mut alt_list = Vec::with_capacity(listeners.len());
|
||||
mem::swap(&mut *listeners, &mut alt_list);
|
||||
let mut items = alt_list.into_iter();
|
||||
while let Some(l) = items.next() {
|
||||
l.sink.send(ev).unwrap();
|
||||
let Reply { resub, outcome } = l.source.recv().unwrap();
|
||||
if resub {
|
||||
listeners.push(l);
|
||||
}
|
||||
match outcome {
|
||||
Ok(res) => {
|
||||
listeners.extend(items);
|
||||
return Some(res);
|
||||
},
|
||||
Err(next) => {
|
||||
ev = next;
|
||||
},
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_one<V>(&self, mut filter: impl FnMut(&T) -> bool, f: impl FnOnce(T) -> (U, V)) -> V {
|
||||
let mut listeners = self.listeners.lock().unwrap();
|
||||
let (sink, request) = sync_channel(0);
|
||||
let (response, source) = sync_channel(0);
|
||||
listeners.push(Listener { sink, source });
|
||||
mem::drop(listeners);
|
||||
loop {
|
||||
let t = request.recv().unwrap();
|
||||
if filter(&t) {
|
||||
let (u, v) = f(t);
|
||||
response.send(Reply { resub: false, outcome: Ok(u) }).unwrap();
|
||||
return v;
|
||||
}
|
||||
response.send(Reply { resub: true, outcome: Err(t) }).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Default for Event<T, U> {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
@@ -14,21 +14,27 @@ use regex::Regex;
|
||||
|
||||
use crate::{api, match_mapping};
|
||||
|
||||
/// A unit of formattable text where the formatter must make a single choice
|
||||
/// Converting from various types via [Into::into] keeps strings intact, but
|
||||
/// [str::parse] resolves escape sequences
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
#[must_use]
|
||||
pub struct FmtUnit {
|
||||
/// Sub-units
|
||||
pub subs: Vec<FmtUnit>,
|
||||
/// Parsed text templates for how to render this text
|
||||
pub variants: Rc<Variants>,
|
||||
}
|
||||
impl FmtUnit {
|
||||
pub fn new(variants: Rc<Variants>, subs: impl IntoIterator<Item = FmtUnit>) -> Self {
|
||||
Self { subs: subs.into_iter().collect(), variants }
|
||||
}
|
||||
/// Deserialize from message
|
||||
pub fn from_api(api: &api::FormattingUnit) -> Self {
|
||||
Self {
|
||||
subs: api.subs.iter().map(Self::from_api).collect(),
|
||||
variants: Rc::new(Variants(
|
||||
(api.variants.iter().map(|var| Variant {
|
||||
(api.variants.iter().map(|var| FmtVariant {
|
||||
bounded: var.bounded,
|
||||
elements: var.elements.iter().map(FmtElement::from_api).collect(),
|
||||
}))
|
||||
@@ -36,6 +42,8 @@ impl FmtUnit {
|
||||
)),
|
||||
}
|
||||
}
|
||||
/// Serialize into message. String interner IDs used in the structure must
|
||||
/// remain valid.
|
||||
pub fn to_api(&self) -> api::FormattingUnit {
|
||||
api::FormattingUnit {
|
||||
subs: self.subs.iter().map(Self::to_api).collect(),
|
||||
@@ -46,11 +54,13 @@ impl FmtUnit {
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
/// Shorthand for a variable-length list that can be formatted in exactly one
|
||||
/// way
|
||||
pub fn sequence(
|
||||
head: &str,
|
||||
delim: &str,
|
||||
tail: &str,
|
||||
seq_bnd: Option<bool>,
|
||||
seq_bnd: bool,
|
||||
seq: impl IntoIterator<Item = FmtUnit>,
|
||||
) -> Self {
|
||||
let items = seq.into_iter().collect_vec();
|
||||
@@ -69,18 +79,37 @@ impl FromStr for FmtUnit {
|
||||
}
|
||||
}
|
||||
|
||||
/// A single element of a format string. Composes into [FmtVariant]
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum FmtElement {
|
||||
Sub { slot: u32, bounded: Option<bool> },
|
||||
/// a reference to an interpolable subunit in the enclosing [FmtUnit]
|
||||
Sub {
|
||||
/// Index into [FmtUnit::subs]
|
||||
slot: u32,
|
||||
/// Whether the subunit can use an unbounded (`Some(false)`) [FmtVariant],
|
||||
/// it is restricted to bounded (`Some(true)`) [FmtVariant], or it should
|
||||
/// inherit this information from the enclosing unit, meaning that the slot
|
||||
/// is at the very end of the format string
|
||||
bounded: Option<bool>,
|
||||
},
|
||||
/// a string snippet
|
||||
String(Rc<String>),
|
||||
/// an indented block
|
||||
Indent(Vec<FmtElement>),
|
||||
}
|
||||
impl FmtElement {
|
||||
/// Create a plain string snippet
|
||||
pub fn str(s: &'_ str) -> Self { Self::String(Rc::new(s.to_string())) }
|
||||
/// Create a slot for a subunit
|
||||
pub fn sub(slot: u32, bounded: Option<bool>) -> Self { Self::Sub { slot, bounded } }
|
||||
/// Create a slot for a subunit's bounded representation
|
||||
pub fn bounded(i: u32) -> Self { Self::sub(i, Some(true)) }
|
||||
/// Create a slot for any representation of a subunit
|
||||
pub fn unbounded(i: u32) -> Self { Self::sub(i, Some(false)) }
|
||||
/// Create an end slot bounded by the enclosing unit if that is bounded
|
||||
pub fn last(i: u32) -> Self { Self::sub(i, None) }
|
||||
/// Create a sequence of `len` unbounded slots capped by a slot of the
|
||||
/// specified boundedness
|
||||
pub fn sequence(len: usize, bounded: Option<bool>) -> Vec<Self> {
|
||||
match len.try_into().unwrap() {
|
||||
0u32 => vec![],
|
||||
@@ -88,6 +117,7 @@ impl FmtElement {
|
||||
n => (0..n - 1).map(FmtElement::unbounded).chain([FmtElement::sub(n - 1, bounded)]).collect(),
|
||||
}
|
||||
}
|
||||
/// Decode from a message
|
||||
pub fn from_api(api: &api::FormattingElement) -> Self {
|
||||
match_mapping!(api, api::FormattingElement => FmtElement {
|
||||
Indent(v => v.iter().map(FmtElement::from_api).collect()),
|
||||
@@ -95,6 +125,7 @@ impl FmtElement {
|
||||
Sub{ *slot, *bounded },
|
||||
})
|
||||
}
|
||||
/// Encode to message
|
||||
pub fn to_api(&self) -> api::FormattingElement {
|
||||
match_mapping!(self, FmtElement => api::FormattingElement {
|
||||
Indent(v => v.iter().map(FmtElement::to_api).collect()),
|
||||
@@ -104,39 +135,16 @@ impl FmtElement {
|
||||
}
|
||||
}
|
||||
|
||||
/// A particular way in which a value may be formatted in text.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct Variant {
|
||||
pub struct FmtVariant {
|
||||
/// Whether this representation has an intrinsic end marker or it needs the
|
||||
/// parent to provide one
|
||||
pub bounded: bool,
|
||||
/// Template string syntax elements
|
||||
pub elements: Vec<FmtElement>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variants_parse_test() {
|
||||
let vars = Rc::new(Variants::default().bounded("({{{0}}})"));
|
||||
let expected_vars = Rc::new(Variants(vec![Variant {
|
||||
bounded: true,
|
||||
elements: vec![
|
||||
FmtElement::String(Rc::new("({".to_string())),
|
||||
FmtElement::Sub { bounded: Some(false), slot: 0 },
|
||||
FmtElement::String(Rc::new("})".to_string())),
|
||||
],
|
||||
}]));
|
||||
assert_eq!(vars.as_ref(), expected_vars.as_ref());
|
||||
let unit = vars.units(["1".into()]);
|
||||
assert_eq!(unit, FmtUnit {
|
||||
subs: vec![FmtUnit {
|
||||
subs: vec![],
|
||||
variants: Rc::new(Variants(vec![Variant {
|
||||
bounded: true,
|
||||
elements: vec![FmtElement::String(Rc::new("1".to_string()))]
|
||||
}]))
|
||||
}],
|
||||
variants: expected_vars
|
||||
});
|
||||
let str = take_first(&unit, true);
|
||||
assert_eq!(str, "({1})");
|
||||
}
|
||||
|
||||
/// Represents a collection of formatting strings for the same set of parameters
|
||||
/// from which the formatter can choose within their associated constraints.
|
||||
///
|
||||
@@ -145,7 +153,7 @@ fn variants_parse_test() {
|
||||
/// - {0l} causes the current end restriction to be applied to the parameter.
|
||||
/// This is to be used if the parameter is at the very end of the variant.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)]
|
||||
pub struct Variants(pub Vec<Variant>);
|
||||
pub struct Variants(pub Vec<FmtVariant>);
|
||||
impl Variants {
|
||||
fn parse_phs(s: &'_ str) -> Vec<FmtElement> {
|
||||
let re = Regex::new(r"(?<tpl>\{\d+?[bl]?\})|(\{\{)|(\}\})").unwrap();
|
||||
@@ -216,7 +224,7 @@ impl Variants {
|
||||
}
|
||||
}
|
||||
fn add(&mut self, bounded: bool, s: &'_ str) {
|
||||
self.0.push(Variant { bounded, elements: Self::parse(s) })
|
||||
self.0.push(FmtVariant { bounded, elements: Self::parse(s) })
|
||||
}
|
||||
/// This option is available in all positions.
|
||||
/// See [Variants] for a description of the format strings
|
||||
@@ -231,35 +239,42 @@ impl Variants {
|
||||
self.add(false, s);
|
||||
self
|
||||
}
|
||||
/// Produces formatting options for `len` parameters separated by `delim`.
|
||||
/// `seq_bnd` indicates whether `delim` and `tail` can unambiguously indicate
|
||||
/// the end of a subsequence. For consistency, the stricter of the two is
|
||||
/// expected to be used
|
||||
pub fn sequence(
|
||||
mut self,
|
||||
len: usize,
|
||||
head: &str,
|
||||
delim: &str,
|
||||
tail: &str,
|
||||
seq_bnd: Option<bool>,
|
||||
seq_bnd: bool,
|
||||
) -> Self {
|
||||
let seq = chain!(
|
||||
[FmtElement::str(head)],
|
||||
Itertools::intersperse(
|
||||
FmtElement::sequence(len, seq_bnd).into_iter(),
|
||||
FmtElement::sequence(len, Some(seq_bnd)).into_iter(),
|
||||
FmtElement::str(delim),
|
||||
),
|
||||
[FmtElement::str(tail)],
|
||||
);
|
||||
self.0.push(Variant { bounded: true, elements: seq.collect_vec() });
|
||||
self.0.push(FmtVariant { bounded: true, elements: seq.collect_vec() });
|
||||
self
|
||||
}
|
||||
/// Pair the slots with subunits to produce a [FmtUnit]
|
||||
pub fn units_own(self, subs: impl IntoIterator<Item = FmtUnit>) -> FmtUnit {
|
||||
FmtUnit::new(Rc::new(self), subs)
|
||||
}
|
||||
/// Pair the slots with subunits to produce a [FmtUnit] by reference. These
|
||||
/// objects should preferably be thread-locally cached whenever possible.
|
||||
pub fn units(self: &Rc<Self>, subs: impl IntoIterator<Item = FmtUnit>) -> FmtUnit {
|
||||
FmtUnit::new(self.clone(), subs)
|
||||
}
|
||||
}
|
||||
impl From<Rc<String>> for Variants {
|
||||
fn from(value: Rc<String>) -> Self {
|
||||
Self(vec![Variant { elements: vec![FmtElement::String(value)], bounded: true }])
|
||||
Self(vec![FmtVariant { elements: vec![FmtElement::String(value)], bounded: true }])
|
||||
}
|
||||
}
|
||||
impl From<String> for Variants {
|
||||
@@ -304,23 +319,18 @@ pub async fn take_first_fmt(v: &(impl Format + ?Sized)) -> String {
|
||||
take_first(&v.print(&FmtCtxImpl { _foo: PhantomData }).await, false)
|
||||
}
|
||||
|
||||
/// [Default] this if you need one
|
||||
#[derive(Default)]
|
||||
pub struct FmtCtxImpl<'a> {
|
||||
_foo: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
pub trait FmtCtx {
|
||||
// fn print_as(&self, p: &(impl Format + ?Sized)) -> impl Future<Output =
|
||||
// String> where Self: Sized {
|
||||
// async {
|
||||
// // for now, always take the first option which is probably the one-line
|
||||
// form let variants = p.print(self).await;
|
||||
// take_first(&variants, true)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
/// Additional settings to the formatter. Implemented by [FmtCtxImpl]. Currently
|
||||
/// not in use
|
||||
pub trait FmtCtx {}
|
||||
impl FmtCtx for FmtCtxImpl<'_> {}
|
||||
|
||||
/// A value that can be formatted into a string with multiple possible forms
|
||||
pub trait Format {
|
||||
#[must_use]
|
||||
fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future<Output = FmtUnit> + 'a;
|
||||
@@ -337,3 +347,37 @@ pub async fn fmt_v<F: Format + ?Sized>(
|
||||
) -> impl Iterator<Item = String> {
|
||||
join_all(v.into_iter().map(|f| async move { take_first_fmt(f.borrow()).await })).await.into_iter()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::format::{FmtElement, FmtUnit, FmtVariant, Variants, take_first};
|
||||
|
||||
#[test]
|
||||
fn variants_parse_test() {
|
||||
let vars = Rc::new(Variants::default().bounded("({{{0}}})"));
|
||||
let expected_vars = Rc::new(Variants(vec![FmtVariant {
|
||||
bounded: true,
|
||||
elements: vec![
|
||||
FmtElement::String(Rc::new("({".to_string())),
|
||||
FmtElement::Sub { bounded: Some(false), slot: 0 },
|
||||
FmtElement::String(Rc::new("})".to_string())),
|
||||
],
|
||||
}]));
|
||||
assert_eq!(vars.as_ref(), expected_vars.as_ref());
|
||||
let unit = vars.units(["1".into()]);
|
||||
assert_eq!(unit, FmtUnit {
|
||||
subs: vec![FmtUnit {
|
||||
subs: vec![],
|
||||
variants: Rc::new(Variants(vec![FmtVariant {
|
||||
bounded: true,
|
||||
elements: vec![FmtElement::String(Rc::new("1".to_string()))]
|
||||
}]))
|
||||
}],
|
||||
variants: expected_vars
|
||||
});
|
||||
let str = take_first(&unit, true);
|
||||
assert_eq!(str, "({1})");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Display;
|
||||
use std::pin::pin;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::task::{Context, Poll, Wake, Waker};
|
||||
use std::thread::panicking;
|
||||
|
||||
use futures::Stream;
|
||||
use itertools::Itertools;
|
||||
use task_local::task_local;
|
||||
|
||||
struct OnPollWaker<F: Fn() + 'static>(Waker, F);
|
||||
impl<F: Fn() + 'static> Wake for OnPollWaker<F> {
|
||||
fn wake(self: Arc<Self>) {
|
||||
(self.1)();
|
||||
self.0.wake_by_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a callback to the [Future] protocol for testing and debugging. Note
|
||||
/// that this function is safe and simple in order to facilitate debugging
|
||||
/// without adding more points of failure, but it's not fast; it performs a heap
|
||||
/// allocation on each poll of the returned future.
|
||||
pub async fn on_wake<F: Future>(
|
||||
f: F,
|
||||
wake: impl Fn() + Clone + Send + Sync + 'static,
|
||||
) -> F::Output {
|
||||
let mut f = pin!(f);
|
||||
futures::future::poll_fn(|cx| {
|
||||
let waker = Arc::new(OnPollWaker(cx.waker().clone(), wake.clone())).into();
|
||||
f.as_mut().poll(&mut Context::from_waker(&waker))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Respond to [Future::poll] with a callback. For maximum flexibility and state
|
||||
/// control, your callback receives the actual poll job as a callback function.
|
||||
/// Failure to call this function will result in an immediate panic.
|
||||
pub async fn wrap_poll<Fut: Future>(
|
||||
f: Fut,
|
||||
mut cb: impl FnMut(Box<dyn FnOnce() -> bool + '_>),
|
||||
) -> Fut::Output {
|
||||
let mut f = pin!(f);
|
||||
futures::future::poll_fn(|cx| {
|
||||
let poll = RefCell::new(None);
|
||||
cb(Box::new(|| {
|
||||
let poll1 = f.as_mut().poll(cx);
|
||||
let ret = poll1.is_ready();
|
||||
*poll.borrow_mut() = Some(poll1);
|
||||
ret
|
||||
}));
|
||||
poll.into_inner().expect("Callback to on_poll failed to call its argument")
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn wrap_poll_next<'a, S: Stream + 'a>(
|
||||
s: S,
|
||||
mut cb: impl FnMut(Box<dyn FnOnce() -> bool + '_>) + 'a,
|
||||
) -> impl Stream<Item = S::Item> + 'a {
|
||||
let mut s = Box::pin(s);
|
||||
futures::stream::poll_fn(move |cx| {
|
||||
let poll = RefCell::new(None);
|
||||
cb(Box::new(|| {
|
||||
let poll1 = s.as_mut().poll_next(cx);
|
||||
let ret = poll1.is_ready();
|
||||
*poll.borrow_mut() = Some(poll1);
|
||||
ret
|
||||
}));
|
||||
poll.into_inner().expect("Callback to on_poll failed to call its argument")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on_stream_wake<'a, S: Stream + 'a>(
|
||||
s: S,
|
||||
wake: impl Fn() + Clone + Send + Sync + 'static,
|
||||
) -> impl Stream<Item = S::Item> {
|
||||
let mut s = Box::pin(s);
|
||||
futures::stream::poll_fn(move |cx| {
|
||||
let waker = Arc::new(OnPollWaker(cx.waker().clone(), wake.clone())).into();
|
||||
s.as_mut().poll_next(&mut Context::from_waker(&waker))
|
||||
})
|
||||
}
|
||||
|
||||
task_local! {
|
||||
static LABEL_STATE: Vec<Rc<String>>
|
||||
}
|
||||
|
||||
pub async fn with_label<Fut: Future>(label: &str, f: Fut) -> Fut::Output {
|
||||
let mut new_lbl = LABEL_STATE.try_with(|lbl| lbl.clone()).unwrap_or_default();
|
||||
new_lbl.push(Rc::new(label.to_string()));
|
||||
LABEL_STATE.scope(new_lbl, f).await
|
||||
}
|
||||
|
||||
pub fn label() -> impl Display + Clone + Send + Sync + 'static {
|
||||
LABEL_STATE.try_with(|lbl| lbl.iter().join("/")).unwrap_or("".to_string())
|
||||
}
|
||||
|
||||
pub struct Label;
|
||||
impl Display for Label {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", label()) }
|
||||
}
|
||||
|
||||
pub async fn eprint_events<Fut: Future>(note: &str, f: Fut) -> Fut::Output {
|
||||
let label = label();
|
||||
let note1 = note.to_string();
|
||||
on_wake(
|
||||
wrap_poll(f, |cb| {
|
||||
eprintln!("{Label} polling {note}");
|
||||
eprintln!("{Label} polled {note} (ready? {})", cb())
|
||||
}),
|
||||
move || eprintln!("{label} woke {note1}"),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn eprint_stream_events<'a, S: Stream + 'a>(
|
||||
note: &'a str,
|
||||
s: S,
|
||||
) -> impl Stream<Item = S::Item> + 'a {
|
||||
let label = label();
|
||||
let note1 = note.to_string();
|
||||
on_stream_wake(
|
||||
wrap_poll_next(s, move |cb| {
|
||||
eprintln!("{Label} polling {note}");
|
||||
eprintln!("{Label} polled {note} (ready? {})", cb())
|
||||
}),
|
||||
move || eprintln!("{label} woke {note1}"),
|
||||
)
|
||||
}
|
||||
|
||||
struct SpinWaker(AtomicBool);
|
||||
impl Wake for SpinWaker {
|
||||
fn wake(self: Arc<Self>) { self.0.store(true, Ordering::Relaxed); }
|
||||
}
|
||||
|
||||
/// A dumb executor that keeps synchronously re-running the future as long as it
|
||||
/// keeps synchronously waking itself.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the future doesn't wake itself and doesn't settle. This is useful for
|
||||
/// deterministic tests that don't contain side effects or threading.
|
||||
pub fn spin_on<Fut: Future>(f: Fut) -> Fut::Output {
|
||||
let repeat = Arc::new(SpinWaker(AtomicBool::new(false)));
|
||||
let mut f = pin!(f);
|
||||
let waker = repeat.clone().into();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
loop {
|
||||
match f.as_mut().poll(&mut cx) {
|
||||
Poll::Ready(t) => break t,
|
||||
Poll::Pending if repeat.0.swap(false, Ordering::Relaxed) => (),
|
||||
Poll::Pending => panic!("The future did not exit and did not call its waker."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an object that will panic if dropped. [PanicOnDrop::defuse] must be
|
||||
/// called once the particular constraint preventing a drop has passed
|
||||
pub fn assert_no_drop(msg: &'static str) -> PanicOnDrop { PanicOnDrop(true, msg) }
|
||||
|
||||
pub struct PanicOnDrop(bool, &'static str);
|
||||
impl PanicOnDrop {
|
||||
pub fn defuse(mut self) { self.0 = false; }
|
||||
}
|
||||
impl Drop for PanicOnDrop {
|
||||
fn drop(&mut self) { assert!(panicking() || !self.0, "{}", self.1) }
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
//! Impure functions that can be triggered by Orchid code when a command
|
||||
//! evaluates to an atom representing a command
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::nort::Expr;
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::error::RTResult;
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
trait_set! {
|
||||
trait Handler = for<'a> Fn(&'a dyn Any, CodeLocation) -> Expr;
|
||||
}
|
||||
|
||||
enum HTEntry<'a> {
|
||||
Handler(Box<dyn Handler + 'a>),
|
||||
Forward(&'a (dyn Handler + 'a)),
|
||||
}
|
||||
impl<'a> AsRef<dyn Handler + 'a> for HTEntry<'a> {
|
||||
fn as_ref(&self) -> &(dyn Handler + 'a) {
|
||||
match self {
|
||||
HTEntry::Handler(h) => &**h,
|
||||
HTEntry::Forward(h) => *h,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A table of impure command handlers exposed to Orchid
|
||||
#[derive(Default)]
|
||||
pub struct HandlerTable<'a> {
|
||||
handlers: HashMap<TypeId, HTEntry<'a>>,
|
||||
}
|
||||
impl<'a> HandlerTable<'a> {
|
||||
/// Create a new [HandlerTable]
|
||||
#[must_use]
|
||||
pub fn new() -> Self { Self { handlers: HashMap::new() } }
|
||||
|
||||
/// Add a handler function to interpret a command and select the continuation.
|
||||
/// See [HandlerTable#with] for a declarative option.
|
||||
pub fn register<T: 'static, R: ToClause>(&mut self, f: impl for<'b> FnMut(&'b T) -> R + 'a) {
|
||||
let cell = RefCell::new(f);
|
||||
let cb = move |a: &dyn Any, loc: CodeLocation| {
|
||||
cell.borrow_mut()(a.downcast_ref().expect("found by TypeId")).to_expr(loc)
|
||||
};
|
||||
let prev = self.handlers.insert(TypeId::of::<T>(), HTEntry::Handler(Box::new(cb)));
|
||||
assert!(prev.is_none(), "A handler for this type is already registered");
|
||||
}
|
||||
|
||||
/// Add a handler function to interpret a command and select the continuation.
|
||||
/// See [HandlerTable#register] for a procedural option.
|
||||
pub fn with<T: 'static>(mut self, f: impl FnMut(&T) -> RTResult<Expr> + 'a) -> Self {
|
||||
self.register(f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Find and execute the corresponding handler for this type
|
||||
pub fn dispatch(&self, arg: &dyn Atomic, loc: CodeLocation) -> Option<Expr> {
|
||||
(self.handlers.get(&arg.as_any_ref().type_id())).map(|ent| ent.as_ref()(arg.as_any_ref(), loc))
|
||||
}
|
||||
|
||||
/// Combine two non-overlapping handler sets
|
||||
#[must_use]
|
||||
pub fn combine(mut self, other: Self) -> Self {
|
||||
for (key, value) in other.handlers {
|
||||
let prev = self.handlers.insert(key, value);
|
||||
assert!(prev.is_none(), "Duplicate handlers")
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add entries that forward requests to a borrowed non-overlapping handler
|
||||
/// set
|
||||
pub fn link<'b: 'a>(mut self, other: &'b HandlerTable<'b>) -> Self {
|
||||
for (key, value) in other.handlers.iter() {
|
||||
let prev = self.handlers.insert(*key, HTEntry::Forward(value.as_ref()));
|
||||
assert!(prev.is_none(), "Duplicate handlers")
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(unconditional_recursion)]
|
||||
#[allow(clippy::ptr_arg)]
|
||||
mod test {
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::HandlerTable;
|
||||
|
||||
/// Ensure that the method I use to verify covariance actually passes with
|
||||
/// covariant and fails with invariant
|
||||
///
|
||||
/// The failing case:
|
||||
/// ```
|
||||
/// struct Cov2<'a>(PhantomData<&'a mut &'a ()>);
|
||||
/// fn fail<'a>(_c: &Cov2<'a>, _s: &'a String) { fail(_c, &String::new()) }
|
||||
/// ```
|
||||
#[allow(unused)]
|
||||
fn covariant_control() {
|
||||
struct Cov<'a>(PhantomData<&'a ()>);
|
||||
fn pass<'a>(_c: &Cov<'a>, _s: &'a String) { pass(_c, &String::new()) }
|
||||
}
|
||||
|
||||
/// The &mut ensures that 'a in the two functions must be disjoint, and that
|
||||
/// ht must outlive both. For this to compile, Rust has to cast ht to the
|
||||
/// shorter lifetimes, ensuring covariance
|
||||
#[allow(unused)]
|
||||
fn assert_covariant() {
|
||||
fn pass<'a>(_ht: HandlerTable<'a>, _s: &'a String) { pass(_ht, &String::new()) }
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,120 @@
|
||||
use std::num::NonZeroU64;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
|
||||
enum Rec<T> {
|
||||
Val(T),
|
||||
Next(usize),
|
||||
}
|
||||
|
||||
/// A simple and very fast store that assigns small stable integer IDs to
|
||||
/// objects. It uses a free-list for O(1) insertion, deletion and retrieval.
|
||||
pub struct IdStore<T> {
|
||||
table: OnceLock<Mutex<HashMap<NonZeroU64, T>>>,
|
||||
id: AtomicU64,
|
||||
first: usize,
|
||||
values: Vec<Rec<T>>,
|
||||
}
|
||||
impl<T> IdStore<T> {
|
||||
pub const fn new() -> Self { Self { table: OnceLock::new(), id: AtomicU64::new(1) } }
|
||||
pub fn add(&self, t: T) -> IdRecord<'_, T> {
|
||||
let tbl = self.table.get_or_init(Mutex::default);
|
||||
let mut tbl_g = tbl.lock().unwrap();
|
||||
let id: NonZeroU64 = self.id.fetch_add(1, Ordering::Relaxed).try_into().unwrap();
|
||||
assert!(tbl_g.insert(id, t).is_none(), "atom ID wraparound");
|
||||
IdRecord(id, tbl_g)
|
||||
pub fn new() -> Self { IdStore { first: 0, values: Vec::new() } }
|
||||
pub fn add(&mut self, value: T) -> usize {
|
||||
if self.first == 0 && self.values.is_empty() {
|
||||
self.first = 1;
|
||||
self.values.push(Rec::Val(value));
|
||||
return 0;
|
||||
}
|
||||
if self.first == self.values.len() {
|
||||
let len = self.values.len();
|
||||
self.values.extend((len..len * 2).map(|i| Rec::Next(i + 1)));
|
||||
}
|
||||
let Some(rec) = self.values.get_mut(self.first) else {
|
||||
panic!("Bounds check and growth above")
|
||||
};
|
||||
let Rec::Next(next) = rec else {
|
||||
panic!("first should always point to an empty space or one past the length")
|
||||
};
|
||||
let id = std::mem::replace(&mut self.first, *next);
|
||||
*rec = Rec::Val(value);
|
||||
id
|
||||
}
|
||||
pub fn get(&self, id: impl Into<NonZeroU64>) -> Option<IdRecord<'_, T>> {
|
||||
let tbl = self.table.get_or_init(Mutex::default);
|
||||
let tbl_g = tbl.lock().unwrap();
|
||||
let id64 = id.into();
|
||||
if tbl_g.contains_key(&id64) { Some(IdRecord(id64, tbl_g)) } else { None }
|
||||
pub fn add_with(&mut self, cb: impl FnOnce(usize) -> T) -> usize { self.add(cb(self.first)) }
|
||||
pub fn remove(&mut self, id: usize) -> T {
|
||||
let Some(rec) = self.values.get_mut(id) else { panic!("Index out of bounds") };
|
||||
let Rec::Val(val) = std::mem::replace(rec, Rec::Next(self.first)) else {
|
||||
panic!("Index vacated")
|
||||
};
|
||||
self.first = id;
|
||||
val
|
||||
}
|
||||
pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
|
||||
(self.values.iter().enumerate())
|
||||
.filter_map(|(i, rec)| if let Rec::Val(val) = rec { Some((i, val)) } else { None })
|
||||
}
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (usize, &mut T)> {
|
||||
(self.values.iter_mut().enumerate())
|
||||
.filter_map(|(i, rec)| if let Rec::Val(val) = rec { Some((i, val)) } else { None })
|
||||
}
|
||||
}
|
||||
#[allow(clippy::type_complexity, reason = "This is verbose enough as it is")]
|
||||
pub struct IntoIter<T>(
|
||||
std::iter::FilterMap<
|
||||
std::iter::Enumerate<std::vec::IntoIter<Rec<T>>>,
|
||||
fn((usize, Rec<T>)) -> Option<(usize, T)>,
|
||||
>,
|
||||
);
|
||||
impl<T> Iterator for IntoIter<T> {
|
||||
type Item = (usize, T);
|
||||
fn next(&mut self) -> Option<Self::Item> { self.0.next() }
|
||||
fn size_hint(&self) -> (usize, Option<usize>) { self.0.size_hint() }
|
||||
}
|
||||
impl<T> IntoIterator for IdStore<T> {
|
||||
type Item = (usize, T);
|
||||
type IntoIter = IntoIter<T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IntoIter(
|
||||
(self.values.into_iter().enumerate())
|
||||
.filter_map(|(i, rec)| if let Rec::Val(val) = rec { Some((i, val)) } else { None }),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<T> Index<usize> for IdStore<T> {
|
||||
type Output = T;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
match self.values.get(index) {
|
||||
Some(Rec::Val(val)) => val,
|
||||
_ => panic!("Invalid or stale index"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> IndexMut<usize> for IdStore<T> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
match self.values.get_mut(index) {
|
||||
Some(Rec::Val(val)) => val,
|
||||
_ => panic!("Invalid or stale index"),
|
||||
}
|
||||
}
|
||||
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) }
|
||||
}
|
||||
|
||||
impl<T> Default for IdStore<T> {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
impl<A> FromIterator<A> for IdStore<A> {
|
||||
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
|
||||
let values = iter.into_iter().map(|a| Rec::Val(a)).collect_vec();
|
||||
Self { first: values.len(), values }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IdRecord<'a, T>(NonZeroU64, MutexGuard<'a, HashMap<NonZeroU64, T>>);
|
||||
impl<T> IdRecord<'_, T> {
|
||||
pub fn id(&self) -> NonZeroU64 { self.0 }
|
||||
pub fn remove(mut self) -> T { self.1.remove(&self.0).unwrap() }
|
||||
}
|
||||
impl<T> Deref for IdRecord<'_, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.1.get(&self.0).expect("Existence checked on construction")
|
||||
}
|
||||
}
|
||||
impl<T> DerefMut for IdRecord<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.1.get_mut(&self.0).expect("Existence checked on construction")
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_and_retrieve() {
|
||||
let mut store = IdStore::new();
|
||||
let key1 = store.add(14);
|
||||
let key2 = store.add(34);
|
||||
assert_eq!(store[key1], 14);
|
||||
assert_eq!(store[key2], 34);
|
||||
assert_eq!(store.remove(key1), 14);
|
||||
assert_eq!(store.iter().collect_vec(), vec![(key2, &34)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,27 @@ use task_local::task_local;
|
||||
|
||||
use crate::api;
|
||||
|
||||
/// Implementation-specific backing object for an interned string.
|
||||
pub trait IStrHandle: AsRef<str> {
|
||||
fn rc(&self) -> Rc<String>;
|
||||
}
|
||||
/// Implementation-specific backing object for an interned sequence of interned
|
||||
/// strings.
|
||||
pub trait IStrvHandle: AsRef<[IStr]> {
|
||||
fn rc(&self) -> Rc<Vec<IStr>>;
|
||||
}
|
||||
|
||||
/// Interned string created with [is] or [es]
|
||||
#[derive(Clone)]
|
||||
pub struct IStr(pub api::TStr, pub Rc<dyn IStrHandle>);
|
||||
impl IStr {
|
||||
/// Obtain a unique ID for this interned data.
|
||||
/// Obtain a unique ID for this interned data
|
||||
///
|
||||
/// NOTICE: the ID is guaranteed to be the same for any interned instance of
|
||||
/// the same value only as long as at least one instance exists. If a value is
|
||||
/// no longer interned, the interner is free to forget about it.
|
||||
/// no longer interned, the interner is free to forget about it
|
||||
pub fn to_api(&self) -> api::TStr { self.0 }
|
||||
/// Owned reference to a shared instance of the interned string
|
||||
pub fn rc(&self) -> Rc<String> { self.1.rc() }
|
||||
}
|
||||
impl Deref for IStr {
|
||||
@@ -45,15 +50,18 @@ impl Display for IStr {
|
||||
impl Debug for IStr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "IStr({self}") }
|
||||
}
|
||||
|
||||
/// Interned string sequence
|
||||
#[derive(Clone)]
|
||||
pub struct IStrv(pub api::TStrv, pub Rc<dyn IStrvHandle>);
|
||||
impl IStrv {
|
||||
/// Obtain a unique ID for this interned data.
|
||||
/// Obtain a unique ID for this interned data
|
||||
///
|
||||
/// NOTICE: the ID is guaranteed to be the same for any interned instance of
|
||||
/// the same value only as long as at least one instance exists. If a value is
|
||||
/// no longer interned, the interner is free to forget about it.
|
||||
/// no longer interned, the interner is free to forget about it
|
||||
pub fn to_api(&self) -> api::TStrv { self.0 }
|
||||
/// Owned reference to a shared instance of the interned sequence
|
||||
pub fn rc(&self) -> Rc<Vec<IStr>> { self.1.rc() }
|
||||
}
|
||||
impl Deref for IStrv {
|
||||
@@ -84,10 +92,23 @@ impl Debug for IStrv {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "IStrv({self})") }
|
||||
}
|
||||
|
||||
/// Injectable interner interface
|
||||
///
|
||||
/// [Self::is] and [Self::iv] return an existing ID if any [IStrHandle] or
|
||||
/// [IStrvHandle] for the same value is still live, and any ID currently not
|
||||
/// used with the same type otherwise
|
||||
///
|
||||
/// [Self::es] and [Self::ev] find an existing value by its key if any
|
||||
/// [IStrHandle] or [IStrvHandle] for the same ID is still live. If all objects
|
||||
/// are gone the functions may work or panic.
|
||||
pub trait InternerSrv {
|
||||
/// Intern a string
|
||||
fn is<'a>(&'a self, v: &'a str) -> LocalBoxFuture<'a, IStr>;
|
||||
/// Find an existing string by its key
|
||||
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr>;
|
||||
/// Intern a str vector
|
||||
fn iv<'a>(&'a self, v: &'a [IStr]) -> LocalBoxFuture<'a, IStrv>;
|
||||
/// Find an existing str vector by its key
|
||||
fn ev(&self, t: api::TStrv) -> LocalBoxFuture<'_, IStrv>;
|
||||
}
|
||||
|
||||
@@ -95,6 +116,8 @@ task_local! {
|
||||
static INTERNER: Rc<dyn InternerSrv>;
|
||||
}
|
||||
|
||||
/// Install a global interner. Within this future, the global [is], [iv], [es]
|
||||
/// and [ev] functions call the provided [InternerSrv]
|
||||
pub async fn with_interner<F: Future>(val: Rc<dyn InternerSrv>, fut: F) -> F::Output {
|
||||
INTERNER.scope(val, fut).await
|
||||
}
|
||||
@@ -103,11 +126,28 @@ fn get_interner() -> Rc<dyn InternerSrv> {
|
||||
INTERNER.try_with(|i| i.clone()).expect("Interner not initialized")
|
||||
}
|
||||
|
||||
/// Intern a `String` (find its ID or assign it a new one)
|
||||
pub async fn is(v: &str) -> IStr { get_interner().is(v).await }
|
||||
/// Intern a `Vec<IStr>` (find its ID or assign it a new one)
|
||||
pub async fn iv(v: &[IStr]) -> IStrv { get_interner().iv(v).await }
|
||||
/// Find a live [IStr] by its ID
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function may panic if there are no other references to the [IStr] we're
|
||||
/// searching for, as the interner is free to forget about unreferenced values
|
||||
pub async fn es(v: api::TStr) -> IStr { get_interner().es(v).await }
|
||||
/// Find a live [IStrv] by its ID
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function may panic if there are no other references to the [IStrv]
|
||||
/// we're searching for, as the interner is free to forget about unreferenced
|
||||
/// values
|
||||
pub async fn ev(v: api::TStrv) -> IStrv { get_interner().ev(v).await }
|
||||
|
||||
/// Basic engine for an interner that supports recovering if a token is not
|
||||
/// found locally.
|
||||
pub mod local_interner {
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::RefCell;
|
||||
@@ -144,6 +184,7 @@ pub mod local_interner {
|
||||
fn new_interned(token: Self::Token, handle: Rc<Handle<Self>>) -> Self::Interned;
|
||||
}
|
||||
|
||||
/// String-specific values for [InternableCard]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct StrBranch;
|
||||
impl InternableCard for StrBranch {
|
||||
@@ -154,6 +195,7 @@ pub mod local_interner {
|
||||
fn new_interned(t: Self::Token, h: Rc<Handle<Self>>) -> Self::Interned { IStr(t, h) }
|
||||
}
|
||||
|
||||
/// Vector-specific values for [InternableCard]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct StrvBranch;
|
||||
impl InternableCard for StrvBranch {
|
||||
@@ -208,8 +250,8 @@ pub mod local_interner {
|
||||
/// Information retained about an interned token indexed both by key and
|
||||
/// value.
|
||||
struct Rec<B: InternableCard> {
|
||||
/// This reference is weak, but the [Drop] handler of [Handle] removes all
|
||||
/// [Rec]s from the interner so it is guaranteed to be live.
|
||||
/// This reference is weak, but the [Drop] handler of [Handle] removes the
|
||||
/// [Rec] from the interner so it is guaranteed to be live.
|
||||
handle: Weak<Handle<B>>,
|
||||
/// Keys for indexing from either table
|
||||
data: Data<B>,
|
||||
|
||||
@@ -17,6 +17,8 @@ impl<'a, I: Iterator<Item = E> + Clone, E: fmt::Display> fmt::Display for PrintL
|
||||
}
|
||||
|
||||
pub trait IteratorPrint: Iterator<Item: fmt::Display> + Clone {
|
||||
/// Pretty-print a list with a comma-separated enumeration and an operator
|
||||
/// word (such as "and" or "or") inserted before the last
|
||||
fn display<'a>(self, operator: &'a str) -> PrintList<'a, Self, Self::Item> {
|
||||
PrintList(self, operator)
|
||||
}
|
||||
@@ -1,33 +1,44 @@
|
||||
pub use async_once_cell;
|
||||
use orchid_api as api;
|
||||
|
||||
pub mod binary;
|
||||
pub mod box_cow;
|
||||
pub mod boxed_iter;
|
||||
pub mod char_filter;
|
||||
mod on_drop;
|
||||
pub use on_drop::*;
|
||||
mod binary;
|
||||
pub use binary::*;
|
||||
mod id_store;
|
||||
pub use id_store::*;
|
||||
mod boxed_iter;
|
||||
pub use boxed_iter::*;
|
||||
mod char_filter;
|
||||
pub use char_filter::*;
|
||||
pub mod clone;
|
||||
pub mod combine;
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
pub mod format;
|
||||
pub mod future_debug;
|
||||
pub mod id_store;
|
||||
pub mod interner;
|
||||
pub mod iter_utils;
|
||||
pub mod join;
|
||||
mod localset;
|
||||
pub mod location;
|
||||
pub mod logging;
|
||||
mod error;
|
||||
pub use error::*;
|
||||
mod format;
|
||||
pub use format::*;
|
||||
mod interner;
|
||||
pub use interner::*;
|
||||
mod iter_print;
|
||||
pub use iter_print::*;
|
||||
mod join;
|
||||
pub use join::*;
|
||||
mod location;
|
||||
pub use location::*;
|
||||
mod logging;
|
||||
pub use logging::*;
|
||||
mod match_mapping;
|
||||
pub mod msg;
|
||||
pub mod name;
|
||||
pub mod number;
|
||||
pub mod parse;
|
||||
pub mod pure_seq;
|
||||
pub mod reqnot;
|
||||
pub mod sequence;
|
||||
pub mod side;
|
||||
pub mod stash;
|
||||
pub mod tl_cache;
|
||||
pub mod tokens;
|
||||
pub mod tree;
|
||||
mod name;
|
||||
pub use name::*;
|
||||
mod number;
|
||||
pub use number::*;
|
||||
mod parse;
|
||||
pub use parse::*;
|
||||
mod comm;
|
||||
pub use comm::*;
|
||||
mod side;
|
||||
pub use side::*;
|
||||
mod stash;
|
||||
pub use stash::*;
|
||||
mod tl_cache;
|
||||
mod tree;
|
||||
pub use tree::*;
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
|
||||
use futures::StreamExt;
|
||||
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender, unbounded};
|
||||
use futures::future::LocalBoxFuture;
|
||||
|
||||
pub struct LocalSet<'a, E> {
|
||||
receiver: UnboundedReceiver<LocalBoxFuture<'a, Result<(), E>>>,
|
||||
pending: VecDeque<LocalBoxFuture<'a, Result<(), E>>>,
|
||||
}
|
||||
impl<'a, E> LocalSet<'a, E> {
|
||||
pub fn new() -> (UnboundedSender<LocalBoxFuture<'a, Result<(), E>>>, Self) {
|
||||
let (sender, receiver) = unbounded();
|
||||
(sender, Self { receiver, pending: VecDeque::new() })
|
||||
}
|
||||
}
|
||||
impl<E> Future for LocalSet<'_, E> {
|
||||
type Output = Result<(), E>;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
let mut any_pending = false;
|
||||
loop {
|
||||
match this.receiver.poll_next_unpin(cx) {
|
||||
Poll::Ready(Some(fut)) => this.pending.push_back(fut),
|
||||
Poll::Ready(None) => break,
|
||||
Poll::Pending => {
|
||||
any_pending = true;
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
let count = this.pending.len();
|
||||
for _ in 0..count {
|
||||
let mut req = this.pending.pop_front().unwrap();
|
||||
match req.as_mut().poll(cx) {
|
||||
Poll::Ready(Ok(())) => (),
|
||||
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
|
||||
Poll::Pending => {
|
||||
any_pending = true;
|
||||
this.pending.push_back(req)
|
||||
},
|
||||
}
|
||||
}
|
||||
if any_pending { Poll::Pending } else { Poll::Ready(Ok(())) }
|
||||
}
|
||||
}
|
||||
@@ -7,25 +7,34 @@ use std::ops::{Add, AddAssign, Range};
|
||||
use futures::future::join_all;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use crate::error::ErrPos;
|
||||
use crate::interner::{IStr, es, is};
|
||||
use crate::name::Sym;
|
||||
use crate::{api, match_mapping, sym};
|
||||
use crate::{ErrPos, IStr, IteratorPrint, Sym, api, es, is, match_mapping, sym};
|
||||
|
||||
trait_set! {
|
||||
pub trait GetSrc = FnMut(&Sym) -> IStr;
|
||||
}
|
||||
|
||||
/// One or more positions in code that are associated with an event, value, or
|
||||
/// other consequence of that code
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Pos {
|
||||
/// Location not known, for example because the expression was decoded from a
|
||||
/// source that doesn't have a meaningful location attached, from a format
|
||||
/// that does not encode location data
|
||||
None,
|
||||
/// If the expression in question is a slot, it receives the substituted
|
||||
/// expression's position. If the expression is being placed into a slot, this
|
||||
/// is discarded. In all other cases, it is a conflict and an error
|
||||
SlotTarget,
|
||||
/// Used in functions to denote the generated code that carries on the
|
||||
/// location of the call. Not allowed in the const tree.
|
||||
/// location of the call. Not allowed in the const tree
|
||||
Inherit,
|
||||
/// ID and parameters of a generator (such as an extension)
|
||||
Gen(CodeGenInfo),
|
||||
/// Range and file
|
||||
SrcRange(SrcRange),
|
||||
/// More than one positions. This vec should not contain another [Pos::Multi]
|
||||
/// and should be `>=2` long. To ensure this, use `+` and `+=` to combine
|
||||
/// positions and do not construct this directly.
|
||||
Multi(Vec<Pos>),
|
||||
}
|
||||
impl Pos {
|
||||
@@ -33,6 +42,7 @@ impl Pos {
|
||||
match self {
|
||||
Self::Gen(g) => g.to_string(),
|
||||
Self::SrcRange(sr) => sr.pretty_print(&get_src(&sr.path)),
|
||||
Self::Multi(posv) => posv.iter().display("and").to_string(),
|
||||
// Can't pretty print partial and meta-location
|
||||
other => format!("{other:?}"),
|
||||
}
|
||||
@@ -106,7 +116,7 @@ impl SrcRange {
|
||||
pub fn new(range: Range<u32>, path: &Sym) -> Self {
|
||||
Self { range: range.clone(), path: path.clone() }
|
||||
}
|
||||
/// Create a dud [SourceRange] for testing. Its value is unspecified and
|
||||
/// Create a dud [SrcRange] for testing. Its value is unspecified and
|
||||
/// volatile.
|
||||
pub async fn mock() -> Self { Self { range: 0..1, path: sym!(test) } }
|
||||
/// Path the source text was loaded from
|
||||
@@ -123,6 +133,10 @@ impl SrcRange {
|
||||
pub fn map_range(&self, map: impl FnOnce(Range<u32>) -> Range<u32>) -> Self {
|
||||
Self { range: map(self.range()), path: self.path() }
|
||||
}
|
||||
/// Format the range in a way that VSCode can convert to a link and is
|
||||
/// human-readable. For this operation we need the source code text, but
|
||||
/// holding it in the position object is a bit heavy so instead we take it as
|
||||
/// an argument
|
||||
pub fn pretty_print(&self, src: &str) -> String {
|
||||
let (sl, sc) = pos2lc(src, self.range.start);
|
||||
let (el, ec) = pos2lc(src, self.range.end);
|
||||
@@ -132,13 +146,21 @@ impl SrcRange {
|
||||
(false, _) => format!("{sl}:{sc}..{el}:{ec}"),
|
||||
}
|
||||
}
|
||||
/// Zero-width position at a certain offset
|
||||
pub fn zw(path: Sym, pos: u32) -> Self { Self { path, range: pos..pos } }
|
||||
/// Deserialize from a message
|
||||
pub async fn from_api(api: &api::SourceRange) -> Self {
|
||||
Self { path: Sym::from_api(api.path).await, range: api.range.clone() }
|
||||
}
|
||||
/// Serialize to a message
|
||||
pub fn to_api(&self) -> api::SourceRange {
|
||||
api::SourceRange { path: self.path.to_api(), range: self.range.clone() }
|
||||
}
|
||||
/// Connect two ranges into one
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if the ranges are not from the same file
|
||||
pub fn to(&self, rhs: &Self) -> Self {
|
||||
assert_eq!(self.path, rhs.path, "Range continues across files");
|
||||
Self { path: self.path(), range: self.start().min(rhs.start())..self.end().max(rhs.end()) }
|
||||
@@ -173,9 +195,11 @@ impl CodeGenInfo {
|
||||
}
|
||||
/// Syntactic location
|
||||
pub fn pos(&self) -> Pos { Pos::Gen(self.clone()) }
|
||||
/// Deserialize from a message
|
||||
pub async fn from_api(api: &api::CodeGenInfo) -> Self {
|
||||
Self { generator: Sym::from_api(api.generator).await, details: es(api.details).await }
|
||||
}
|
||||
/// Serialize to a message
|
||||
pub fn to_api(&self) -> api::CodeGenInfo {
|
||||
api::CodeGenInfo { generator: self.generator.to_api(), details: self.details.to_api() }
|
||||
}
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Arguments;
|
||||
use std::io::Write;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::future::LocalBoxFuture;
|
||||
use task_local::task_local;
|
||||
|
||||
use crate::api;
|
||||
|
||||
task_local! {
|
||||
static DEFAULT_WRITER: RefCell<Box<dyn Write>>
|
||||
}
|
||||
|
||||
/// Set the stream used for [api::LogStrategy::Default]. If not set,
|
||||
/// [std::io::stderr] will be used.
|
||||
pub async fn with_default_stream<F: Future>(stderr: impl Write + 'static, fut: F) -> F::Output {
|
||||
DEFAULT_WRITER.scope(RefCell::new(Box::new(stderr)), fut).await
|
||||
}
|
||||
use crate::{api, clone};
|
||||
|
||||
/// A first argument to [write!] and [writeln!] that causes them to return a
|
||||
/// future.
|
||||
pub trait LogWriter {
|
||||
fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()>;
|
||||
}
|
||||
|
||||
/// Injectable logging service passed to [with_logger]
|
||||
pub trait Logger: Any {
|
||||
/// Obtain a writer that processes the message according to the given category
|
||||
fn writer(&self, category: &str) -> Rc<dyn LogWriter>;
|
||||
/// Obtain a serializable limited description of what will eventually happen
|
||||
/// to the message
|
||||
fn strat(&self, category: &str) -> api::LogStrategy;
|
||||
/// Determine whether a certain category would get processed at all. This is a
|
||||
/// shortcut
|
||||
fn is_active(&self, category: &str) -> bool {
|
||||
!matches!(self.strat(category), api::LogStrategy::Discard)
|
||||
}
|
||||
@@ -35,43 +31,46 @@ task_local! {
|
||||
static LOGGER: Rc<dyn Logger>;
|
||||
}
|
||||
|
||||
/// Within the future passed to this function the freestanding [log] and
|
||||
/// [get_logger] functions can be used
|
||||
pub async fn with_logger<F: Future>(logger: impl Logger + 'static, fut: F) -> F::Output {
|
||||
LOGGER.scope(Rc::new(logger), fut).await
|
||||
}
|
||||
|
||||
/// Obtain an async log writer
|
||||
///
|
||||
/// ```
|
||||
/// use orchid_base::log;
|
||||
/// async {
|
||||
/// let user = "lorentz";
|
||||
/// writeln!(log("info"), "Hello from {user}").await
|
||||
/// };
|
||||
/// ```
|
||||
pub fn log(category: &str) -> Rc<dyn LogWriter> {
|
||||
LOGGER.try_with(|l| l.writer(category)).expect("Logger not set!")
|
||||
}
|
||||
|
||||
/// Obtain a reference to the current [Logger]. This is mostly useful for
|
||||
/// [Logger::is_active]-based optimizations
|
||||
pub fn get_logger() -> Rc<dyn Logger> { LOGGER.try_with(|l| l.clone()).expect("Logger not set!") }
|
||||
|
||||
pub mod test {
|
||||
use std::fmt::Arguments;
|
||||
use std::rc::Rc;
|
||||
|
||||
use futures::future::LocalBoxFuture;
|
||||
|
||||
use crate::clone;
|
||||
use crate::logging::{LogWriter, Logger};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestLogger(Rc<dyn Fn(String) -> LocalBoxFuture<'static, ()>>);
|
||||
impl LogWriter for TestLogger {
|
||||
fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> {
|
||||
(self.0)(fmt.to_string())
|
||||
}
|
||||
}
|
||||
impl Logger for TestLogger {
|
||||
fn strat(&self, _category: &str) -> orchid_api::LogStrategy { orchid_api::LogStrategy::Default }
|
||||
fn writer(&self, _category: &str) -> std::rc::Rc<dyn LogWriter> { Rc::new(self.clone()) }
|
||||
}
|
||||
impl TestLogger {
|
||||
pub fn new(f: impl AsyncFn(String) + 'static) -> Self {
|
||||
let f = Rc::new(f);
|
||||
Self(Rc::new(move |s| clone!(f; Box::pin(async move { f(s).await }))))
|
||||
}
|
||||
}
|
||||
impl Default for TestLogger {
|
||||
fn default() -> Self { TestLogger::new(async |s| eprint!("{s}")) }
|
||||
#[derive(Clone)]
|
||||
pub struct TestLogger(Rc<dyn Fn(String) -> LocalBoxFuture<'static, ()>>);
|
||||
impl LogWriter for TestLogger {
|
||||
fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> {
|
||||
(self.0)(fmt.to_string())
|
||||
}
|
||||
}
|
||||
impl Logger for TestLogger {
|
||||
fn strat(&self, _category: &str) -> orchid_api::LogStrategy { orchid_api::LogStrategy::Default }
|
||||
fn writer(&self, _category: &str) -> std::rc::Rc<dyn LogWriter> { Rc::new(self.clone()) }
|
||||
}
|
||||
impl TestLogger {
|
||||
pub fn new(f: impl AsyncFn(String) + 'static) -> Self {
|
||||
let f = Rc::new(f);
|
||||
Self(Rc::new(move |s| clone!(f; Box::pin(async move { f(s).await }))))
|
||||
}
|
||||
}
|
||||
impl Default for TestLogger {
|
||||
fn default() -> Self { TestLogger::new(async |s| eprint!("{s}")) }
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
|
||||
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use orchid_api_traits::{Decode, Encode};
|
||||
|
||||
pub async fn send_msg(mut write: Pin<&mut impl AsyncWrite>, msg: &[u8]) -> io::Result<()> {
|
||||
let mut len_buf = vec![];
|
||||
let len_prefix = u32::try_from(msg.len()).expect("Message over 4GB not permitted on channel");
|
||||
len_prefix.encode_vec(&mut len_buf);
|
||||
write.write_all(&len_buf).await?;
|
||||
write.write_all(msg).await?;
|
||||
write.flush().await
|
||||
}
|
||||
|
||||
pub async fn recv_msg(mut read: Pin<&mut impl AsyncRead>) -> io::Result<Vec<u8>> {
|
||||
let mut len_buf = [0u8; (u32::BITS / 8) as usize];
|
||||
read.read_exact(&mut len_buf).await?;
|
||||
let len = u32::decode(Pin::new(&mut &len_buf[..])).await?;
|
||||
let mut msg = vec![0u8; len as usize];
|
||||
read.read_exact(&mut msg).await?;
|
||||
Ok(msg)
|
||||
}
|
||||
@@ -11,8 +11,7 @@ use futures::future::{OptionFuture, join_all};
|
||||
use itertools::Itertools;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use crate::api;
|
||||
use crate::interner::{IStr, IStrv, es, ev, is, iv};
|
||||
use crate::{IStr, IStrv, api, es, ev, is, iv};
|
||||
|
||||
trait_set! {
|
||||
/// Traits that all name iterators should implement
|
||||
@@ -143,7 +142,6 @@ impl VName {
|
||||
}
|
||||
/// Read a `::` separated namespaced name
|
||||
pub async fn parse(s: &str) -> Result<Self, EmptyNameError> { Self::new(VPath::parse(s).await) }
|
||||
pub async fn literal(s: &'static str) -> Self { Self::parse(s).await.expect("empty literal !?") }
|
||||
/// Obtain an iterator over the segments of the name
|
||||
pub fn iter(&self) -> impl Iterator<Item = IStr> + '_ { self.0.iter().cloned() }
|
||||
}
|
||||
@@ -213,10 +211,13 @@ impl Sym {
|
||||
pub fn id(&self) -> NonZeroU64 { self.0.to_api().0 }
|
||||
/// Extern the sym for editing
|
||||
pub fn to_vname(&self) -> VName { VName(self[..].to_vec()) }
|
||||
/// Decode from a message
|
||||
pub async fn from_api(marker: api::TStrv) -> Sym {
|
||||
Self::from_tok(ev(marker).await).expect("Empty sequence found for serialized Sym")
|
||||
}
|
||||
/// Encode into a message
|
||||
pub fn to_api(&self) -> api::TStrv { self.tok().to_api() }
|
||||
/// Copy the symbol and extend it with a suffix
|
||||
pub async fn suffix(&self, tokv: impl IntoIterator<Item = IStr>) -> Sym {
|
||||
Self::new(self.0.iter().cloned().chain(tokv)).await.unwrap()
|
||||
}
|
||||
@@ -246,7 +247,7 @@ impl Deref for Sym {
|
||||
|
||||
/// An abstraction over tokenized vs non-tokenized names so that they can be
|
||||
/// handled together in datastructures. The names can never be empty
|
||||
#[allow(clippy::len_without_is_empty)] // never empty
|
||||
#[allow(clippy::len_without_is_empty, reason = "never empty")]
|
||||
pub trait NameLike:
|
||||
'static + Clone + Eq + Hash + fmt::Debug + fmt::Display + Borrow<[IStr]>
|
||||
{
|
||||
@@ -292,11 +293,11 @@ impl NameLike for VName {}
|
||||
#[macro_export]
|
||||
macro_rules! sym {
|
||||
($seg1:tt $( :: $seg:tt)*) => {
|
||||
$crate::tl_cache!(async $crate::name::Sym : {
|
||||
$crate::name::Sym::from_tok(
|
||||
$crate::interner::iv(&[
|
||||
$crate::interner::is($crate::sym!(@SEG $seg1)).await
|
||||
$( , $crate::interner::is($crate::sym!(@SEG $seg)).await )*
|
||||
$crate::tl_cache!(async $crate::Sym : {
|
||||
$crate::Sym::from_tok(
|
||||
$crate::iv(&[
|
||||
$crate::is($crate::sym!(@SEG $seg1)).await
|
||||
$( , $crate::is($crate::sym!(@SEG $seg)).await )*
|
||||
])
|
||||
.await
|
||||
).unwrap()
|
||||
@@ -316,10 +317,10 @@ macro_rules! sym {
|
||||
#[macro_export]
|
||||
macro_rules! vname {
|
||||
($seg1:tt $( :: $seg:tt)*) => {
|
||||
$crate::tl_cache!(async $crate::name::VName : {
|
||||
$crate::name::VName::new([
|
||||
$crate::interner::is(stringify!($seg1)).await
|
||||
$( , $crate::interner::is(stringify!($seg)).await )*
|
||||
$crate::tl_cache!(async $crate::VName : {
|
||||
$crate::VName::new([
|
||||
$crate::is(stringify!($seg1)).await
|
||||
$( , $crate::is(stringify!($seg)).await )*
|
||||
]).unwrap()
|
||||
})
|
||||
};
|
||||
@@ -331,28 +332,26 @@ macro_rules! vname {
|
||||
#[macro_export]
|
||||
macro_rules! vpath {
|
||||
($seg1:tt $( :: $seg:tt)*) => {
|
||||
$crate::tl_cache!(async $crate::name::VPath : {
|
||||
$crate::name::VPath(vec![
|
||||
$crate::interner::is(stringify!($seg1)).await
|
||||
$( , $crate::interner::is(stringify!($seg)).await )*
|
||||
$crate::tl_cache!(async $crate::VPath : {
|
||||
$crate::VPath::new(vec![
|
||||
$crate::is(stringify!($seg1)).await
|
||||
$( , $crate::is(stringify!($seg)).await )*
|
||||
])
|
||||
})
|
||||
};
|
||||
() => {
|
||||
$crate::name::VPath(vec![])
|
||||
$crate::VPath::new(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
mod test {
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use orchid_api_traits::spin_on;
|
||||
|
||||
use super::{NameLike, Sym, VName};
|
||||
use crate::interner::local_interner::local_interner;
|
||||
use crate::interner::{IStr, is, with_interner};
|
||||
use crate::name::VPath;
|
||||
use crate::local_interner::local_interner;
|
||||
use crate::{IStr, NameLike, Sym, VName, VPath, is, with_interner};
|
||||
|
||||
#[test]
|
||||
pub fn recur() {
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
//! The NORT (Normal Order Referencing Tree) is the interpreter's runtime
|
||||
//! representation of Orchid programs.
|
||||
//!
|
||||
//! It uses a locator tree to find bound variables in lambda functions, which
|
||||
//! necessitates a normal reduction order because modifying the body by reducing
|
||||
//! expressions would invalidate any locators in enclosing lambdas.
|
||||
//!
|
||||
//! Clauses are held in a mutable `Arc<Mutex<_>>`, so that after substitution
|
||||
//! the instances of the argument remain linked and a reduction step applied to
|
||||
//! any instance transforms all of them.
|
||||
//!
|
||||
//! To improve locality and make the tree less deep and locators shorter,
|
||||
//! function calls store multiple arguments in a deque.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Mutex, MutexGuard, TryLockError};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::path_set::PathSet;
|
||||
use crate::foreign::atom::Atom;
|
||||
#[allow(unused)] // for doc
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::error::{RTErrorObj, RTResult};
|
||||
use crate::foreign::try_from_expr::TryFromExpr;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
#[allow(unused)] // for doc
|
||||
use crate::parse::parsed;
|
||||
use crate::utils::ddispatch::request;
|
||||
|
||||
/// Kinda like [AsMut] except it supports a guard
|
||||
pub(crate) trait AsDerefMut<T> {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = T> + '_;
|
||||
}
|
||||
|
||||
/// An expression with metadata
|
||||
#[derive(Clone)]
|
||||
pub struct Expr {
|
||||
/// The actual value
|
||||
pub clause: ClauseInst,
|
||||
/// Information about the code that produced this value
|
||||
pub location: CodeLocation,
|
||||
}
|
||||
impl Expr {
|
||||
/// Constructor
|
||||
pub fn new(clause: ClauseInst, location: CodeLocation) -> Self { Self { clause, location } }
|
||||
/// Obtain the location of the expression
|
||||
pub fn location(&self) -> CodeLocation { self.location.clone() }
|
||||
|
||||
/// Convert into any type that implements [TryFromExpr]. Calls to this
|
||||
/// function are generated wherever a conversion is elided in an extern
|
||||
/// function.
|
||||
pub fn downcast<T: TryFromExpr>(self) -> RTResult<T> {
|
||||
let Expr { mut clause, location } = self;
|
||||
loop {
|
||||
let cls_deref = clause.cls_mut();
|
||||
match &*cls_deref {
|
||||
Clause::Identity(alt) => {
|
||||
let temp = alt.clone();
|
||||
drop(cls_deref);
|
||||
clause = temp;
|
||||
},
|
||||
_ => {
|
||||
drop(cls_deref);
|
||||
return T::from_expr(Expr { clause, location });
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Visit all expressions in the tree. The search can be exited early by
|
||||
/// returning [Some]
|
||||
///
|
||||
/// See also [parsed::Expr::search_all]
|
||||
pub fn search_all<T>(&self, predicate: &mut impl FnMut(&Self) -> Option<T>) -> Option<T> {
|
||||
if let Some(t) = predicate(self) {
|
||||
return Some(t);
|
||||
}
|
||||
self.clause.inspect(|c| match c {
|
||||
Clause::Identity(_alt) => unreachable!("Handled by inspect"),
|
||||
Clause::Apply { f, x } =>
|
||||
(f.search_all(predicate)).or_else(|| x.iter().find_map(|x| x.search_all(predicate))),
|
||||
Clause::Lambda { body, .. } => body.search_all(predicate),
|
||||
Clause::Constant(_) | Clause::LambdaArg | Clause::Atom(_) | Clause::Bottom(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Clone the refcounted [ClauseInst] out of the expression
|
||||
#[must_use]
|
||||
pub fn clsi(&self) -> ClauseInst { self.clause.clone() }
|
||||
|
||||
/// Read-Write access to the [Clause]
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if the clause is already borrowed
|
||||
pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.clause.cls_mut() }
|
||||
}
|
||||
|
||||
impl fmt::Debug for Expr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}@{}", self.clause, self.location)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Expr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.clause) }
|
||||
}
|
||||
|
||||
impl AsDerefMut<Clause> for Expr {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self.clause.cls_mut() }
|
||||
}
|
||||
|
||||
/// A wrapper around expressions to handle their multiple occurences in
|
||||
/// the tree together
|
||||
#[derive(Clone)]
|
||||
pub struct ClauseInst(pub Arc<Mutex<Clause>>);
|
||||
impl ClauseInst {
|
||||
/// Wrap a [Clause] in a shared container so that normalization steps are
|
||||
/// applied to all references
|
||||
#[must_use]
|
||||
pub fn new(cls: Clause) -> Self { Self(Arc::new(Mutex::new(cls))) }
|
||||
|
||||
/// Take the [Clause] out of this container if it's the last reference to it,
|
||||
/// or return self.
|
||||
pub fn try_unwrap(self) -> Result<Clause, ClauseInst> {
|
||||
Arc::try_unwrap(self.0).map(|c| c.into_inner().unwrap()).map_err(Self)
|
||||
}
|
||||
|
||||
/// Read-Write access to the shared clause instance
|
||||
///
|
||||
/// if the clause is already borrowed, this will block until it is released.
|
||||
pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.0.lock().unwrap() }
|
||||
|
||||
/// Call a predicate on the clause, returning whatever the
|
||||
/// predicate returns. This is a convenience function for reaching
|
||||
/// through the [Mutex]. The clause will never be [Clause::Identity].
|
||||
#[must_use]
|
||||
pub fn inspect<T>(&self, predicate: impl FnOnce(&Clause) -> T) -> T {
|
||||
match &*self.cls_mut() {
|
||||
Clause::Identity(sub) => sub.inspect(predicate),
|
||||
x => predicate(x),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this expression is an [Atomic], request an object of the given type.
|
||||
/// If it's not an atomic, fail the request automatically.
|
||||
#[must_use = "your request might not have succeeded"]
|
||||
pub fn request<T: 'static>(&self) -> Option<T> {
|
||||
match &*self.cls_mut() {
|
||||
Clause::Atom(a) => request(&*a.0),
|
||||
Clause::Identity(alt) => alt.request(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Associate a location with this clause
|
||||
pub fn into_expr(self, location: CodeLocation) -> Expr {
|
||||
Expr { clause: self.clone(), location: location.clone() }
|
||||
}
|
||||
/// Check ahead-of-time if this clause contains an atom. Calls
|
||||
/// [ClauseInst#cls] for read access.
|
||||
///
|
||||
/// Since atoms cannot become normalizable, if this is true and previous
|
||||
/// normalization failed, the atom is known to be in normal form.
|
||||
pub fn is_atom(&self) -> bool { matches!(&*self.cls_mut(), Clause::Atom(_)) }
|
||||
|
||||
/// Tries to unwrap the [Arc]. If that fails, clones it field by field.
|
||||
/// If it's a [Clause::Atom] which cannot be cloned, wraps it in a
|
||||
/// [Clause::Identity].
|
||||
///
|
||||
/// Implementation of [crate::foreign::to_clause::ToClause::to_clause]. The
|
||||
/// trait is more general so it requires a location which this one doesn't.
|
||||
pub fn into_cls(self) -> Clause {
|
||||
self.try_unwrap().unwrap_or_else(|clsi| match &*clsi.cls_mut() {
|
||||
Clause::Apply { f, x } => Clause::Apply { f: f.clone(), x: x.clone() },
|
||||
Clause::Atom(_) => Clause::Identity(clsi.clone()),
|
||||
Clause::Bottom(e) => Clause::Bottom(e.clone()),
|
||||
Clause::Constant(c) => Clause::Constant(c.clone()),
|
||||
Clause::Identity(sub) => Clause::Identity(sub.clone()),
|
||||
Clause::Lambda { args, body } => Clause::Lambda { args: args.clone(), body: body.clone() },
|
||||
Clause::LambdaArg => Clause::LambdaArg,
|
||||
})
|
||||
}
|
||||
|
||||
/// Decides if this clause is the exact same instance as another. Most useful
|
||||
/// to detect potential deadlocks.
|
||||
pub fn is_same(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) }
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClauseInst {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr:?}"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ClauseInst {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr}"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsDerefMut<Clause> for ClauseInst {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self.cls_mut() }
|
||||
}
|
||||
|
||||
/// Distinct types of expressions recognized by the interpreter
|
||||
#[derive(Debug)]
|
||||
pub enum Clause {
|
||||
/// An expression that causes an error
|
||||
Bottom(RTErrorObj),
|
||||
/// Indicates that this [ClauseInst] has the same value as the other
|
||||
/// [ClauseInst]. This has two benefits;
|
||||
///
|
||||
/// - [Clause] and therefore [Atomic] doesn't have to be [Clone] which saves
|
||||
/// many synchronization primitives and reference counters in usercode
|
||||
/// - it enforces on the type level that all copies are normalized together,
|
||||
/// so accidental inefficiency in the interpreter is rarer.
|
||||
///
|
||||
/// That being said, it's still arbitrary many indirections, so when possible
|
||||
/// APIs should be usable with a [ClauseInst] directly.
|
||||
Identity(ClauseInst),
|
||||
/// An opaque non-callable value, eg. a file handle
|
||||
Atom(Atom),
|
||||
/// A function application
|
||||
Apply {
|
||||
/// Function to be applied
|
||||
f: Expr,
|
||||
/// Argument to be substituted in the function
|
||||
x: VecDeque<Expr>,
|
||||
},
|
||||
/// A name to be looked up in the interpreter's symbol table
|
||||
Constant(Sym),
|
||||
/// A function
|
||||
Lambda {
|
||||
/// A collection of (zero or more) paths to placeholders belonging to this
|
||||
/// function
|
||||
args: Option<PathSet>,
|
||||
/// The tree produced by this function, with placeholders where the
|
||||
/// argument will go
|
||||
body: Expr,
|
||||
},
|
||||
/// A placeholder within a function that will be replaced upon application
|
||||
LambdaArg,
|
||||
}
|
||||
impl Clause {
|
||||
/// Wrap a clause in a refcounted lock
|
||||
pub fn into_inst(self) -> ClauseInst { ClauseInst::new(self) }
|
||||
/// Wrap a clause in an expression.
|
||||
pub fn into_expr(self, location: CodeLocation) -> Expr { self.into_inst().into_expr(location) }
|
||||
}
|
||||
|
||||
impl fmt::Display for Clause {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Clause::Atom(a) => write!(f, "{a:?}"),
|
||||
Clause::Bottom(err) => write!(f, "bottom({err})"),
|
||||
Clause::LambdaArg => write!(f, "arg"),
|
||||
Clause::Apply { f: fun, x } => write!(f, "({fun} {})", x.iter().join(" ")),
|
||||
Clause::Lambda { args, body } => match args {
|
||||
Some(path) => write!(f, "[\\{path}.{body}]"),
|
||||
None => write!(f, "[\\_.{body}]"),
|
||||
},
|
||||
Clause::Constant(t) => write!(f, "{t}"),
|
||||
Clause::Identity(other) => write!(f, "{{{other}}}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsDerefMut<Clause> for Clause {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self }
|
||||
}
|
||||
@@ -3,10 +3,7 @@ use std::ops::Range;
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use crate::error::{OrcErrv, mk_errv};
|
||||
use crate::interner::is;
|
||||
use crate::location::SrcRange;
|
||||
use crate::name::Sym;
|
||||
use crate::{OrcErrv, SrcRange, Sym, is, mk_errv};
|
||||
|
||||
/// A number, either floating point or unsigned int, parsed by Orchid.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
//! Adaptor trait to embed Rust values in Orchid expressions
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use never::Never;
|
||||
|
||||
use super::error::{RTError, RTResult};
|
||||
use crate::interpreter::context::{RunEnv, RunParams};
|
||||
use crate::interpreter::nort;
|
||||
use crate::location::{CodeLocation, SourceRange};
|
||||
use crate::name::NameLike;
|
||||
use crate::parse::lexer::Lexeme;
|
||||
use crate::parse::parsed;
|
||||
use crate::utils::ddispatch::{request, Request, Responder};
|
||||
|
||||
/// Information returned by [Atomic::run].
|
||||
pub enum AtomicReturn {
|
||||
/// No work was done. If the atom takes an argument, it can be provided now
|
||||
Inert(Atom),
|
||||
/// Work was done, returns new clause and consumed gas. 1 gas is already
|
||||
/// consumed by the virtual call, so nonzero values indicate expensive
|
||||
/// operations.
|
||||
Change(usize, nort::Clause),
|
||||
}
|
||||
impl AtomicReturn {
|
||||
/// Report indicating that the value is inert. The result here is always [Ok],
|
||||
/// it's meant to match the return type of [Atomic::run]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub fn inert<T: Atomic, E>(this: T) -> Result<Self, E> { Ok(Self::Inert(Atom::new(this))) }
|
||||
}
|
||||
|
||||
/// Returned by [Atomic::run]
|
||||
pub type AtomicResult = RTResult<AtomicReturn>;
|
||||
|
||||
/// General error produced when a non-function [Atom] is applied to something as
|
||||
/// a function.
|
||||
#[derive(Clone)]
|
||||
pub struct NotAFunction(pub nort::Expr);
|
||||
impl RTError for NotAFunction {}
|
||||
impl fmt::Display for NotAFunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?} is not a function", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about a function call presented to an external function
|
||||
pub struct CallData<'a, 'b> {
|
||||
/// Location of the function expression
|
||||
pub location: CodeLocation,
|
||||
/// The argument the function was called on. Functions are curried
|
||||
pub arg: nort::Expr,
|
||||
/// Globally available information such as the list of all constants
|
||||
pub env: &'a RunEnv<'b>,
|
||||
/// Resource limits and other details specific to this interpreter run
|
||||
pub params: &'a mut RunParams,
|
||||
}
|
||||
|
||||
/// Information about a normalization run presented to an atom
|
||||
pub struct RunData<'a, 'b> {
|
||||
/// Location of the atom
|
||||
pub location: CodeLocation,
|
||||
/// Globally available information such as the list of all constants
|
||||
pub env: &'a RunEnv<'b>,
|
||||
/// Resource limits and other details specific to this interpreter run
|
||||
pub params: &'a mut RunParams,
|
||||
}
|
||||
|
||||
/// Functionality the interpreter needs to handle a value
|
||||
///
|
||||
/// # Lifecycle methods
|
||||
///
|
||||
/// Atomics expose the methods [Atomic::redirect], [Atomic::run],
|
||||
/// [Atomic::apply] and [Atomic::apply_mut] to interact with the interpreter.
|
||||
/// The interpreter first tries to call `redirect` to find a subexpression to
|
||||
/// normalize. If it returns `None` or the subexpression is inert, `run` is
|
||||
/// called. `run` takes ownership of the value and returns a new one.
|
||||
///
|
||||
/// If `run` indicated in its return value that the result is inert and the atom
|
||||
/// is in the position of a function, `apply` or `apply_mut` is called depending
|
||||
/// upon whether the atom is referenced elsewhere. `apply` falls back to
|
||||
/// `apply_mut` so implementing it is considered an optimization to avoid
|
||||
/// excessive copying.
|
||||
///
|
||||
/// Atoms don't generally have to be copyable because clauses are refcounted in
|
||||
/// the interpreter, but Orchid code is always free to duplicate the references
|
||||
/// and apply them as functions to multiple different arguments so atoms that
|
||||
/// represent functions have to support application by-ref without consuming the
|
||||
/// function itself.
|
||||
pub trait Atomic: Any + fmt::Debug + Responder + Send
|
||||
where Self: 'static
|
||||
{
|
||||
/// Casts this value to [Any] so that its original value can be salvaged
|
||||
/// during introspection by other external code.
|
||||
///
|
||||
/// This function should be implemented in exactly one way:
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
|
||||
/// ```
|
||||
#[must_use]
|
||||
fn as_any(self: Box<Self>) -> Box<dyn Any>;
|
||||
/// See [Atomic::as_any], exactly the same but for references
|
||||
#[must_use]
|
||||
fn as_any_ref(&self) -> &dyn Any;
|
||||
/// Print the atom's type name. Should only ever be implemented as
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn type_name(&self) -> &'static str { std::any::type_name::<Self>() }
|
||||
/// ```
|
||||
fn type_name(&self) -> &'static str;
|
||||
|
||||
/// Returns a reference to a possible expression held inside the atom which
|
||||
/// can be reduced. For an overview of the lifecycle see [Atomic]
|
||||
fn redirect(&mut self) -> Option<&mut nort::Expr>;
|
||||
|
||||
/// Attempt to normalize this value. If it wraps a value, this should report
|
||||
/// inert. If it wraps a computation, it should execute one logical step of
|
||||
/// the computation and return a structure representing the next.
|
||||
///
|
||||
/// For an overview of the lifecycle see [Atomic]
|
||||
fn run(self: Box<Self>, run: RunData) -> AtomicResult;
|
||||
|
||||
/// Combine the function with an argument to produce a new clause. Falls back
|
||||
/// to [Atomic::apply_mut] by default.
|
||||
///
|
||||
/// For an overview of the lifecycle see [Atomic]
|
||||
fn apply(mut self: Box<Self>, call: CallData) -> RTResult<nort::Clause> { self.apply_mut(call) }
|
||||
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
///
|
||||
/// For an overview of the lifecycle see [Atomic]
|
||||
fn apply_mut(&mut self, call: CallData) -> RTResult<nort::Clause>;
|
||||
|
||||
/// Must return true for atoms parsed from identical source.
|
||||
/// If the atom cannot be parsed from source, it can safely be ignored
|
||||
#[allow(unused_variables)]
|
||||
fn parser_eq(&self, other: &dyn Atomic) -> bool { false }
|
||||
|
||||
/// Wrap the atom in a clause to be placed in an [AtomicResult].
|
||||
#[must_use]
|
||||
fn atom_cls(self) -> nort::Clause
|
||||
where Self: Sized {
|
||||
nort::Clause::Atom(Atom(Box::new(self)))
|
||||
}
|
||||
|
||||
/// Shorthand for `self.atom_cls().to_inst()`
|
||||
fn atom_clsi(self) -> nort::ClauseInst
|
||||
where Self: Sized {
|
||||
self.atom_cls().into_inst()
|
||||
}
|
||||
|
||||
/// Wrap the atom in a new expression instance to be placed in a tree
|
||||
#[must_use]
|
||||
fn atom_expr(self, location: CodeLocation) -> nort::Expr
|
||||
where Self: Sized {
|
||||
self.atom_clsi().into_expr(location)
|
||||
}
|
||||
|
||||
/// Wrap the atom in a clause to be placed in a
|
||||
/// [crate::parse::parsed::SourceLine].
|
||||
#[must_use]
|
||||
fn ast_cls(self) -> parsed::Clause
|
||||
where Self: Sized + Clone {
|
||||
parsed::Clause::Atom(AtomGenerator::cloner(self))
|
||||
}
|
||||
|
||||
/// Wrap the atom in an expression to be placed in a
|
||||
/// [crate::parse::parsed::SourceLine].
|
||||
#[must_use]
|
||||
fn ast_exp<N: NameLike>(self, range: SourceRange) -> parsed::Expr
|
||||
where Self: Sized + Clone {
|
||||
self.ast_cls().into_expr(range)
|
||||
}
|
||||
|
||||
/// Wrap this atomic value in a lexeme. This means that the atom will
|
||||
/// participate in macro reproject, so it must implement [Atomic::parser_eq].
|
||||
fn lexeme(self) -> Lexeme
|
||||
where Self: Sized + Clone {
|
||||
Lexeme::Atom(AtomGenerator::cloner(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct for generating any number of [Atom]s. Since atoms aren't Clone,
|
||||
/// this represents the ability to create any number of instances of an atom
|
||||
#[derive(Clone)]
|
||||
pub struct AtomGenerator(Arc<dyn Fn() -> Atom + Send + Sync>);
|
||||
impl AtomGenerator {
|
||||
/// Use a factory function to create any number of atoms
|
||||
pub fn new(f: impl Fn() -> Atom + Send + Sync + 'static) -> Self { Self(Arc::new(f)) }
|
||||
/// Clone a representative atom when called
|
||||
pub fn cloner(atom: impl Atomic + Clone) -> Self {
|
||||
let lock = Mutex::new(atom);
|
||||
Self::new(move || Atom::new(lock.lock().unwrap().clone()))
|
||||
}
|
||||
/// Generate an atom
|
||||
pub fn run(&self) -> Atom { self.0() }
|
||||
}
|
||||
impl fmt::Debug for AtomGenerator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.run()) }
|
||||
}
|
||||
impl PartialEq for AtomGenerator {
|
||||
fn eq(&self, other: &Self) -> bool { self.run().0.parser_eq(&*other.run().0) }
|
||||
}
|
||||
|
||||
/// Represents a black box unit of data with its own normalization steps.
|
||||
/// Typically Rust functions integrated with [super::fn_bridge::xfn] will
|
||||
/// produce and consume [Atom]s to represent both raw data, pending
|
||||
/// computational tasks, and curried partial calls awaiting their next argument.
|
||||
pub struct Atom(pub Box<dyn Atomic>);
|
||||
impl Atom {
|
||||
/// Wrap an [Atomic] in a type-erased box
|
||||
#[must_use]
|
||||
pub fn new<T: 'static + Atomic>(data: T) -> Self { Self(Box::new(data) as Box<dyn Atomic>) }
|
||||
/// Get the contained data
|
||||
#[must_use]
|
||||
pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic }
|
||||
/// Test the type of the contained data without downcasting
|
||||
#[must_use]
|
||||
pub fn is<T: Atomic>(&self) -> bool { self.data().as_any_ref().is::<T>() }
|
||||
/// Downcast contained data, panic if it isn't the specified type
|
||||
#[must_use]
|
||||
pub fn downcast<T: Atomic>(self) -> T {
|
||||
*self.0.as_any().downcast().expect("Type mismatch on Atom::cast")
|
||||
}
|
||||
/// Normalize the contained data
|
||||
pub fn run(self, run: RunData<'_, '_>) -> AtomicResult { self.0.run(run) }
|
||||
/// Request a delegate from the encapsulated data
|
||||
pub fn request<T: 'static>(&self) -> Option<T> { request(self.0.as_ref()) }
|
||||
/// Downcast the atom to a concrete atomic type, or return the original atom
|
||||
/// if it is not the specified type
|
||||
pub fn try_downcast<T: Atomic>(self) -> Result<T, Self> {
|
||||
match self.0.as_any_ref().is::<T>() {
|
||||
true => Ok(*self.0.as_any().downcast().expect("checked just above")),
|
||||
false => Err(self),
|
||||
}
|
||||
}
|
||||
/// Downcast an atom by reference
|
||||
pub fn downcast_ref<T: Atomic>(&self) -> Option<&T> { self.0.as_any_ref().downcast_ref() }
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
pub fn apply(self, call: CallData) -> RTResult<nort::Clause> { self.0.apply(call) }
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
pub fn apply_mut(&mut self, call: CallData) -> RTResult<nort::Clause> { self.0.apply_mut(call) }
|
||||
}
|
||||
|
||||
impl fmt::Debug for Atom {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.data()) }
|
||||
}
|
||||
|
||||
impl Responder for Never {
|
||||
fn respond(&self, _request: Request) { match *self {} }
|
||||
}
|
||||
impl Atomic for Never {
|
||||
fn as_any(self: Box<Self>) -> Box<dyn Any> { match *self {} }
|
||||
fn as_any_ref(&self) -> &dyn Any { match *self {} }
|
||||
fn type_name(&self) -> &'static str { match *self {} }
|
||||
fn redirect(&mut self) -> Option<&mut nort::Expr> { match *self {} }
|
||||
fn run(self: Box<Self>, _: RunData) -> AtomicResult { match *self {} }
|
||||
fn apply_mut(&mut self, _: CallData) -> RTResult<nort::Clause> { match *self {} }
|
||||
}
|
||||
5
orchid-base/src/on_drop.rs
Normal file
5
orchid-base/src/on_drop.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub struct OnDrop<F: FnOnce()>(Option<F>);
|
||||
impl<F: FnOnce()> Drop for OnDrop<F> {
|
||||
fn drop(&mut self) { (self.0.take().unwrap())() }
|
||||
}
|
||||
pub fn on_drop<F: FnOnce()>(f: F) -> OnDrop<F> { OnDrop(Some(f)) }
|
||||
@@ -6,17 +6,19 @@ use futures::FutureExt;
|
||||
use futures::future::join_all;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::api;
|
||||
use crate::error::{OrcErrv, OrcRes, mk_errv, report};
|
||||
use crate::format::{FmtCtx, FmtUnit, Format, fmt};
|
||||
use crate::interner::{IStr, es, is};
|
||||
use crate::location::SrcRange;
|
||||
use crate::name::{Sym, VName, VPath};
|
||||
use crate::tree::{ExprRepr, ExtraTok, Paren, TokTree, Token, ttv_fmt, ttv_range};
|
||||
use crate::{
|
||||
ExprRepr, ExtraTok, FmtCtx, FmtUnit, Format, IStr, OrcErrv, OrcRes, Paren, SrcRange, Sym,
|
||||
TokTree, Token, VName, VPath, api, es, fmt, is, mk_errv, report, ttv_fmt, ttv_range,
|
||||
};
|
||||
|
||||
/// A character that can appear at the start of a name; `[a-zA-Z_]`
|
||||
pub fn name_start(c: char) -> bool { c.is_alphabetic() || c == '_' }
|
||||
/// A character that can appear inside a name after the start `[a-zA-Z0-9_]`
|
||||
pub fn name_char(c: char) -> bool { name_start(c) || c.is_numeric() }
|
||||
pub fn op_char(c: char) -> bool { !name_char(c) && !unrep_space(c) && !"()[]{}\\".contains(c) }
|
||||
/// A character that can appear in an operator. Anything except
|
||||
/// `a-zA-Z0-9_()[]{}\` or whitespace
|
||||
pub fn op_char(c: char) -> bool { !name_char(c) && !c.is_whitespace() && !"()[]{}\\".contains(c) }
|
||||
/// Any whitespace except a line break
|
||||
pub fn unrep_space(c: char) -> bool { c.is_whitespace() && !"\r\n".contains(c) }
|
||||
|
||||
/// A cheaply copiable subsection of a document that holds onto context data and
|
||||
@@ -31,7 +33,10 @@ where
|
||||
A: ExprRepr,
|
||||
X: ExtraTok,
|
||||
{
|
||||
/// Create a snippet from a fallback token for position tracking and a range
|
||||
/// of tokens
|
||||
pub fn new(prev: &'a TokTree<A, X>, cur: &'a [TokTree<A, X>]) -> Self { Self { prev, cur } }
|
||||
/// Split snippet at index
|
||||
pub fn split_at(self, pos: u32) -> (Self, Self) {
|
||||
let Self { prev, cur } = self;
|
||||
let fst = Self { prev, cur: &cur[..pos as usize] };
|
||||
@@ -39,23 +44,35 @@ where
|
||||
let snd = Self { prev: new_prev, cur: &self.cur[pos as usize..] };
|
||||
(fst, snd)
|
||||
}
|
||||
/// Find the first index that matches a condition
|
||||
pub fn find_idx(self, mut f: impl FnMut(&Token<A, X>) -> bool) -> Option<u32> {
|
||||
self.cur.iter().position(|t| f(&t.tok)).map(|t| t as u32)
|
||||
}
|
||||
/// Get the n-th token
|
||||
pub fn get(self, idx: u32) -> Option<&'a TokTree<A, X>> { self.cur.get(idx as usize) }
|
||||
/// Count how many tokens long the current sequence is. Parenthesized
|
||||
/// subsequences count as 1
|
||||
pub fn len(self) -> u32 { self.cur.len() as u32 }
|
||||
/// The fallback token that can be used for error reporting if this snippet is
|
||||
/// unexpectedly empty
|
||||
pub fn prev(self) -> &'a TokTree<A, X> { self.prev }
|
||||
/// Create a position that describes all tokens in this snippet
|
||||
pub fn sr(self) -> SrcRange { ttv_range(self.cur).unwrap_or_else(|| self.prev.sr.clone()) }
|
||||
pub fn pop_front(self) -> Option<(&'a TokTree<A, X>, Self)> {
|
||||
/// Split the first token
|
||||
pub fn split_first(self) -> Option<(&'a TokTree<A, X>, Self)> {
|
||||
self.cur.first().map(|r| (r, self.split_at(1).1))
|
||||
}
|
||||
pub fn pop_back(self) -> Option<(Self, &'a TokTree<A, X>)> {
|
||||
/// Split the last token
|
||||
pub fn split_last(self) -> Option<(Self, &'a TokTree<A, X>)> {
|
||||
self.cur.last().map(|r| (self.split_at(self.len() - 1).0, r))
|
||||
}
|
||||
/// Split the snippet at the first token that matches the predicate
|
||||
pub fn split_once(self, f: impl FnMut(&Token<A, X>) -> bool) -> Option<(Self, Self)> {
|
||||
let idx = self.find_idx(f)?;
|
||||
Some((self.split_at(idx).0, self.split_at(idx + 1).1))
|
||||
}
|
||||
/// Split the snippet at each occurrence of a delimiter matched by the
|
||||
/// predicate
|
||||
pub fn split(mut self, mut f: impl FnMut(&Token<A, X>) -> bool) -> impl Iterator<Item = Self> {
|
||||
iter::from_fn(move || {
|
||||
if self.is_empty() {
|
||||
@@ -66,7 +83,10 @@ where
|
||||
Some(ret)
|
||||
})
|
||||
}
|
||||
pub fn is_empty(self) -> bool { self.len() == 0 }
|
||||
/// Returns true if the snippet contains no tokens. Note that thanks to the
|
||||
/// fallback, an empty snippet still has a queriable position
|
||||
pub fn is_empty(self) -> bool { self.cur.is_empty() }
|
||||
/// Skip tokens that are not meant to be significant inside expressions
|
||||
pub fn skip_fluff(self) -> Self {
|
||||
let non_fluff_start = self.find_idx(|t| !matches!(t, Token::BR | Token::Comment(_)));
|
||||
self.split_at(non_fluff_start.unwrap_or(self.len())).1
|
||||
@@ -86,6 +106,7 @@ impl<A: ExprRepr, X: ExtraTok> Format for Snippet<'_, A, X> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A comment as parsed from code
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Comment {
|
||||
pub text: IStr,
|
||||
@@ -114,6 +135,15 @@ impl fmt::Display for Comment {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "--[{}]--", self.text) }
|
||||
}
|
||||
|
||||
/// Split a snippet into items by line breaks outside parentheses, unwrap lines
|
||||
/// that are entirely wrapped in a single set of round parens for multiline
|
||||
/// items, and associate all comments with the next non-comment line
|
||||
///
|
||||
/// The parse result's [Parsed::output] is the comments, the line's contents are
|
||||
/// held in [Parsed::tail] so semantically your line parser "continues" for each
|
||||
/// line separately
|
||||
///
|
||||
/// This is the procedure for module parsing
|
||||
pub async fn line_items<'a, A: ExprRepr, X: ExtraTok>(
|
||||
snip: Snippet<'a, A, X>,
|
||||
) -> Vec<Parsed<'a, Vec<Comment>, A, X>> {
|
||||
@@ -141,10 +171,11 @@ pub async fn line_items<'a, A: ExprRepr, X: ExtraTok>(
|
||||
items
|
||||
}
|
||||
|
||||
/// Pop the next token that isn't a comment or line break
|
||||
pub async fn try_pop_no_fluff<'a, A: ExprRepr, X: ExtraTok>(
|
||||
snip: Snippet<'a, A, X>,
|
||||
) -> ParseRes<'a, &'a TokTree<A, X>, A, X> {
|
||||
match snip.skip_fluff().pop_front() {
|
||||
match snip.skip_fluff().split_first() {
|
||||
Some((output, tail)) => Ok(Parsed { output, tail }),
|
||||
None =>
|
||||
Err(mk_errv(is("Unexpected end").await, "Line ends abruptly; more tokens were expected", [
|
||||
@@ -153,6 +184,7 @@ pub async fn try_pop_no_fluff<'a, A: ExprRepr, X: ExtraTok>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Fail if the snippet isn't empty
|
||||
pub async fn expect_end(snip: Snippet<'_, impl ExprRepr, impl ExtraTok>) -> OrcRes<()> {
|
||||
match snip.skip_fluff().get(0) {
|
||||
Some(surplus) => Err(mk_errv(
|
||||
@@ -164,6 +196,7 @@ pub async fn expect_end(snip: Snippet<'_, impl ExprRepr, impl ExtraTok>) -> OrcR
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a token and ensure that it matches the specified keyword
|
||||
pub async fn expect_tok<'a, A: ExprRepr, X: ExtraTok>(
|
||||
snip: Snippet<'a, A, X>,
|
||||
tok: IStr,
|
||||
@@ -179,6 +212,8 @@ pub async fn expect_tok<'a, A: ExprRepr, X: ExtraTok>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Report an error related to a token that can conveniently use the token's
|
||||
/// text representation in the long message
|
||||
pub async fn token_errv<A: ExprRepr, X: ExtraTok>(
|
||||
tok: &TokTree<A, X>,
|
||||
description: &'static str,
|
||||
@@ -187,17 +222,21 @@ pub async fn token_errv<A: ExprRepr, X: ExtraTok>(
|
||||
mk_errv(is(description).await, message(&fmt(tok).await), [tok.sr.pos()])
|
||||
}
|
||||
|
||||
/// Success output of parsers
|
||||
pub struct Parsed<'a, T, H: ExprRepr, X: ExtraTok> {
|
||||
/// Information obtained from consumed tokens
|
||||
pub output: T,
|
||||
/// Input to next parser
|
||||
pub tail: Snippet<'a, H, X>,
|
||||
}
|
||||
|
||||
pub type ParseRes<'a, T, H, X> = OrcRes<Parsed<'a, T, H, X>>;
|
||||
|
||||
/// Parse a `namespaced::name` or a `namespaced::(multi name)`
|
||||
pub async fn parse_multiname<'a, A: ExprRepr, X: ExtraTok>(
|
||||
tail: Snippet<'a, A, X>,
|
||||
) -> ParseRes<'a, Vec<Import>, A, X> {
|
||||
let Some((tt, tail)) = tail.skip_fluff().pop_front() else {
|
||||
let Some((tt, tail)) = tail.skip_fluff().split_first() else {
|
||||
return Err(mk_errv(
|
||||
is("Expected token").await,
|
||||
"Expected a name, a parenthesized list of names, or a globstar.",
|
||||
@@ -226,7 +265,7 @@ pub async fn parse_multiname<'a, A: ExprRepr, X: ExtraTok>(
|
||||
Token::S(Paren::Round, b) => {
|
||||
let mut o = Vec::new();
|
||||
let mut body = Snippet::new(tt, b);
|
||||
while let Some((output, tail)) = body.pop_front() {
|
||||
while let Some((output, tail)) = body.split_first() {
|
||||
match rec(output).boxed_local().await {
|
||||
Ok(names) => o.extend(names),
|
||||
Err(e) => report(e),
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
//! Methods to operate on Rust vectors in a declarative manner
|
||||
|
||||
use std::iter;
|
||||
|
||||
/// Pure version of [Vec::push]
|
||||
///
|
||||
/// Create a new vector consisting of the provided vector with the
|
||||
/// element appended. See [pushed_ref] to use it with a slice
|
||||
pub fn pushed<I: IntoIterator, C: FromIterator<I::Item>>(vec: I, t: I::Item) -> C {
|
||||
vec.into_iter().chain(iter::once(t)).collect()
|
||||
}
|
||||
|
||||
/// Pure version of [Vec::push]
|
||||
///
|
||||
/// Create a new vector consisting of the provided slice with the
|
||||
/// element appended. See [pushed] for the owned version
|
||||
pub fn pushed_ref<'a, T: Clone + 'a, C: FromIterator<T>>(
|
||||
vec: impl IntoIterator<Item = &'a T>,
|
||||
t: T,
|
||||
) -> C {
|
||||
vec.into_iter().cloned().chain(iter::once(t)).collect()
|
||||
}
|
||||
|
||||
/// Push an element on the adhoc stack, pass it to the callback, then pop the
|
||||
/// element out again.
|
||||
pub fn with_pushed<T, U>(
|
||||
vec: &mut Vec<T>,
|
||||
item: T,
|
||||
cb: impl for<'a> FnOnce(&'a mut Vec<T>) -> U,
|
||||
) -> (T, U) {
|
||||
vec.push(item);
|
||||
let out = cb(vec);
|
||||
let item = vec.pop().expect("top element stolen by callback");
|
||||
(item, out)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
//! An alternative to `Iterable` in many languages, a [Fn] that returns an
|
||||
//! iterator.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::boxed_iter::BoxedIter;
|
||||
|
||||
trait_set! {
|
||||
trait Payload<'a, T> = Fn() -> BoxedIter<'a, T> + 'a;
|
||||
}
|
||||
|
||||
/// Dynamic iterator building callback. Given how many trait objects this
|
||||
/// involves, it may actually be slower than C#.
|
||||
pub struct Sequence<'a, T: 'a>(Rc<dyn Payload<'a, T>>);
|
||||
impl<'a, T: 'a> Sequence<'a, T> {
|
||||
/// Construct from a concrete function returning a concrete iterator
|
||||
pub fn new<I: IntoIterator<Item = T> + 'a>(f: impl Fn() -> I + 'a) -> Self {
|
||||
Self(Rc::new(move || Box::new(f().into_iter())))
|
||||
}
|
||||
/// Get an iterator from the function
|
||||
pub fn iter(&self) -> BoxedIter<'_, T> { (self.0)() }
|
||||
}
|
||||
impl<'a, T: 'a> Clone for Sequence<'a, T> {
|
||||
fn clone(&self) -> Self { Self(self.0.clone()) }
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::fmt;
|
||||
use std::ops::Not;
|
||||
|
||||
use crate::boxed_iter::BoxedIter;
|
||||
use itertools::Either;
|
||||
|
||||
/// A primitive for encoding the two sides Left and Right. While booleans
|
||||
/// are technically usable for this purpose, they're very easy to confuse
|
||||
@@ -67,10 +67,13 @@ impl Side {
|
||||
}
|
||||
/// Walk a double ended iterator (assumed to be left-to-right) in this
|
||||
/// direction
|
||||
pub fn walk<'a, I: DoubleEndedIterator + 'a>(&self, iter: I) -> BoxedIter<'a, I::Item> {
|
||||
pub fn walk<'a, I: DoubleEndedIterator + 'a>(
|
||||
&self,
|
||||
iter: I,
|
||||
) -> impl Iterator<Item = I::Item> + 'a {
|
||||
match self {
|
||||
Side::Right => Box::new(iter) as BoxedIter<I::Item>,
|
||||
Side::Left => Box::new(iter.rev()),
|
||||
Side::Right => Either::Right(iter),
|
||||
Side::Left => Either::Left(iter.rev()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/// Cache a value in a [thread_local!]. Supports synchronous and asynchronous
|
||||
/// initializers
|
||||
///
|
||||
/// ```
|
||||
/// #[macro_use]
|
||||
/// use orchid_base::tl_cache;
|
||||
///
|
||||
/// // simple synchronous case
|
||||
/// let foo = tl_cache!(Rc<Vec<usize>>: vec![0; 1024]);
|
||||
/// async {
|
||||
/// async fn complex_operation(x: usize) -> usize { x + 1 }
|
||||
/// // async case
|
||||
/// let bar = tl_cache!(async usize: complex_operation(0).await)
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! tl_cache {
|
||||
($ty:ty : $expr:expr) => {{
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
pub use api::Paren;
|
||||
|
||||
use crate::api;
|
||||
|
||||
pub const PARENS: &[(char, char, Paren)] =
|
||||
&[('(', ')', Paren::Round), ('[', ']', Paren::Square), ('{', '}', Paren::Curly)];
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
@@ -12,62 +11,67 @@ use never::Never;
|
||||
use orchid_api_traits::Coding;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use crate::error::OrcErrv;
|
||||
use crate::format::{FmtCtx, FmtUnit, Format, Variants};
|
||||
use crate::interner::{IStr, es};
|
||||
use crate::location::{Pos, SrcRange};
|
||||
use crate::name::{Sym, VName, VPath};
|
||||
use crate::parse::Snippet;
|
||||
use crate::{api, match_mapping, tl_cache};
|
||||
use crate::{
|
||||
FmtCtx, FmtUnit, Format, IStr, OrcErrv, Pos, Snippet, SrcRange, Sym, VName, VPath, Variants, api,
|
||||
es, match_mapping, tl_cache,
|
||||
};
|
||||
|
||||
/// The 3 types of parentheses Orchid's lexer recognizes as intrinsic groups in
|
||||
/// the S-tree
|
||||
pub type Paren = api::Paren;
|
||||
|
||||
/// Helper table with different kinds of parentheses recognized by the language.
|
||||
/// opening, closing, variant name
|
||||
pub const PARENS: &[(char, char, Paren)] =
|
||||
&[('(', ')', Paren::Round), ('[', ']', Paren::Square), ('{', '}', Paren::Curly)];
|
||||
|
||||
/// Extension interface for embedded expressions and expression construction
|
||||
/// commands inside token trees
|
||||
pub trait TokenVariant<ApiEquiv: Clone + Debug + Coding>: Format + Clone + fmt::Debug {
|
||||
/// Additional arguments to the deserializer. If deserialization of a token
|
||||
/// type is impossible, set this to a sentinel unit type that describes why.
|
||||
/// If you set this to [Never], your token tree type can never be
|
||||
/// deserialized.
|
||||
type FromApiCtx<'a>;
|
||||
/// Additional arguments to the serializer. If serialization of a token type
|
||||
/// is forbidden, set this to a sentinel unit type that describes how to avoid
|
||||
/// it.
|
||||
/// If you set this to [Never], your token tree type can never be serialized.
|
||||
type ToApiCtx<'a>;
|
||||
/// Deserializer
|
||||
#[must_use]
|
||||
fn from_api(
|
||||
api: &ApiEquiv,
|
||||
api: ApiEquiv,
|
||||
ctx: &mut Self::FromApiCtx<'_>,
|
||||
pos: SrcRange,
|
||||
) -> impl Future<Output = Self>;
|
||||
/// Serializer
|
||||
#[must_use]
|
||||
fn into_api(self, ctx: &mut Self::ToApiCtx<'_>) -> impl Future<Output = ApiEquiv>;
|
||||
}
|
||||
impl<T: Clone + Debug + Coding> TokenVariant<T> for Never {
|
||||
type FromApiCtx<'a> = ();
|
||||
type ToApiCtx<'a> = ();
|
||||
async fn from_api(_: &T, _: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
|
||||
async fn from_api(_: T, _: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
|
||||
panic!("Cannot deserialize Never")
|
||||
}
|
||||
async fn into_api(self, _: &mut Self::ToApiCtx<'_>) -> T { match self {} }
|
||||
}
|
||||
|
||||
trait_set! {
|
||||
// TokenHandle
|
||||
/// [api::Token::Handle] variant
|
||||
pub trait ExprRepr = TokenVariant<api::ExprTicket>;
|
||||
// TokenExpr
|
||||
/// [api::Token::NewExpr] variant
|
||||
pub trait ExtraTok = TokenVariant<api::Expression>;
|
||||
}
|
||||
|
||||
trait_set! {
|
||||
/// Callback to callback to [recur].
|
||||
pub trait RecurCB<H: ExprRepr, X: ExtraTok> = Fn(TokTree<H, X>) -> TokTree<H, X>;
|
||||
}
|
||||
|
||||
pub fn recur<H: ExprRepr, X: ExtraTok>(
|
||||
tt: TokTree<H, X>,
|
||||
f: &impl Fn(TokTree<H, X>, &dyn RecurCB<H, X>) -> TokTree<H, X>,
|
||||
) -> TokTree<H, X> {
|
||||
f(tt, &|TokTree { sr: range, tok }| {
|
||||
let tok = match tok {
|
||||
tok @ (Token::BR | Token::Bottom(_) | Token::Comment(_) | Token::Name(_)) => tok,
|
||||
tok @ (Token::Handle(_) | Token::NewExpr(_)) => tok,
|
||||
Token::NS(n, b) => Token::NS(n, Box::new(recur(*b, f))),
|
||||
Token::LambdaHead(arg) => Token::LambdaHead(Box::new(recur(*arg, f))),
|
||||
Token::S(p, b) => Token::S(p, b.into_iter().map(|tt| recur(tt, f)).collect_vec()),
|
||||
};
|
||||
TokTree { sr: range, tok }
|
||||
})
|
||||
}
|
||||
|
||||
/// An atom that can be passed through the API boundary as part of an
|
||||
/// expression. In particular, atoms created by extensions use this form.
|
||||
pub trait AtomRepr: Clone + Format {
|
||||
type Ctx: ?Sized;
|
||||
#[must_use]
|
||||
@@ -93,6 +97,7 @@ impl Display for TokHandle<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Handle({})", self.0.0) }
|
||||
}
|
||||
|
||||
/// Lexer output
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TokTree<H: ExprRepr, X: ExtraTok> {
|
||||
pub tok: Token<H, X>,
|
||||
@@ -102,22 +107,37 @@ pub struct TokTree<H: ExprRepr, X: ExtraTok> {
|
||||
pub sr: SrcRange,
|
||||
}
|
||||
impl<H: ExprRepr, X: ExtraTok> TokTree<H, X> {
|
||||
/// Visit all tokens, modify them at will, and optionally recurse into them by
|
||||
/// calling the callback passed to your callback
|
||||
pub fn recur(self, f: &impl Fn(Self, &dyn RecurCB<H, X>) -> Self) -> Self {
|
||||
f(self, &|TokTree { sr: range, tok }| {
|
||||
let tok = match tok {
|
||||
tok @ (Token::BR | Token::Bottom(_) | Token::Comment(_) | Token::Name(_)) => tok,
|
||||
tok @ (Token::Handle(_) | Token::NewExpr(_)) => tok,
|
||||
Token::NS(n, b) => Token::NS(n, Box::new(b.recur(f))),
|
||||
Token::LambdaHead(arg) => Token::LambdaHead(Box::new(arg.recur(f))),
|
||||
Token::S(p, b) => Token::S(p, b.into_iter().map(|tt| tt.recur(f)).collect_vec()),
|
||||
};
|
||||
TokTree { sr: range, tok }
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn from_api(
|
||||
tt: &api::TokenTree,
|
||||
tt: api::TokenTree,
|
||||
hctx: &mut H::FromApiCtx<'_>,
|
||||
xctx: &mut X::FromApiCtx<'_>,
|
||||
src: &Sym,
|
||||
) -> Self {
|
||||
let pos = SrcRange::new(tt.range.clone(), src);
|
||||
let tok = match_mapping!(&tt.token, api::Token => Token::<H, X> {
|
||||
let pos = SrcRange::new(tt.range, src);
|
||||
let tok = match_mapping!(tt.token, api::Token => Token::<H, X> {
|
||||
BR,
|
||||
NS(n => es(*n).await,
|
||||
b => Box::new(Self::from_api(b, hctx, xctx, src).boxed_local().await)),
|
||||
NS(n => es(n).await,
|
||||
b => Box::new(Self::from_api(*b, hctx, xctx, src).boxed_local().await)),
|
||||
Bottom(e => OrcErrv::from_api(e).await),
|
||||
LambdaHead(arg => Box::new(Self::from_api(arg, hctx, xctx, src).boxed_local().await)),
|
||||
Name(n => es(*n).await),
|
||||
S(*par, b => ttv_from_api(b, hctx, xctx, src).await),
|
||||
Comment(c => es(*c).await),
|
||||
LambdaHead(arg => Box::new(Self::from_api(*arg, hctx, xctx, src).boxed_local().await)),
|
||||
Name(n => es(n).await),
|
||||
S(par, b => ttv_from_api(b, hctx, xctx, src).await),
|
||||
Comment(c => es(c).await),
|
||||
NewExpr(expr => X::from_api(expr, xctx, pos.clone()).await),
|
||||
Handle(tk => H::from_api(tk, hctx, pos.clone()).await)
|
||||
});
|
||||
@@ -186,21 +206,22 @@ impl<H: ExprRepr, X: ExtraTok> Format for TokTree<H, X> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive a token sequence from API
|
||||
pub async fn ttv_from_api<H: ExprRepr, X: ExtraTok>(
|
||||
tokv: impl IntoIterator<Item: Borrow<api::TokenTree>>,
|
||||
tokv: impl IntoIterator<Item = api::TokenTree>,
|
||||
hctx: &mut H::FromApiCtx<'_>,
|
||||
xctx: &mut X::FromApiCtx<'_>,
|
||||
src: &Sym,
|
||||
) -> Vec<TokTree<H, X>> {
|
||||
stream(async |mut cx| {
|
||||
for tok in tokv {
|
||||
cx.emit(TokTree::<H, X>::from_api(tok.borrow(), hctx, xctx, src).boxed_local().await).await
|
||||
cx.emit(TokTree::<H, X>::from_api(tok, hctx, xctx, src).boxed_local().await).await
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await
|
||||
}
|
||||
|
||||
/// Encode a token sequence for sending
|
||||
pub async fn ttv_into_api<H: ExprRepr, X: ExtraTok>(
|
||||
tokv: impl IntoIterator<Item = TokTree<H, X>>,
|
||||
hctx: &mut H::ToApiCtx<'_>,
|
||||
@@ -215,6 +236,7 @@ pub async fn ttv_into_api<H: ExprRepr, X: ExtraTok>(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Enclose the tokens in `()` if there is more than one
|
||||
pub fn wrap_tokv<H: ExprRepr, X: ExtraTok>(
|
||||
items: impl IntoIterator<Item = TokTree<H, X>>,
|
||||
) -> TokTree<H, X> {
|
||||
@@ -229,8 +251,6 @@ pub fn wrap_tokv<H: ExprRepr, X: ExtraTok>(
|
||||
}
|
||||
}
|
||||
|
||||
pub use api::Paren;
|
||||
|
||||
/// Lexer output variant
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Token<H: ExprRepr, X: ExtraTok> {
|
||||
@@ -272,8 +292,10 @@ impl<H: ExprRepr, X: ExtraTok> Format for Token<H, X> {
|
||||
async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||
match self {
|
||||
Self::BR => "\n".to_string().into(),
|
||||
Self::Bottom(err) if err.len() == 1 => format!("Bottom({}) ", err.one().unwrap()).into(),
|
||||
Self::Bottom(err) => format!("Botttom(\n{}) ", indent(&err.to_string())).into(),
|
||||
Self::Bottom(err) => match err.one() {
|
||||
Some(err) => format!("Bottom({err}) ").into(),
|
||||
None => format!("Botttom(\n{}) ", indent(&err.to_string())).into(),
|
||||
},
|
||||
Self::Comment(c) => format!("--[{c}]--").into(),
|
||||
Self::LambdaHead(arg) =>
|
||||
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("\\{0b}.")))
|
||||
@@ -295,16 +317,20 @@ impl<H: ExprRepr, X: ExtraTok> Format for Token<H, X> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the location that best describes a sequence of tokens if the sequence
|
||||
/// isn't empty
|
||||
pub fn ttv_range<'a>(ttv: &[TokTree<impl ExprRepr + 'a, impl ExtraTok + 'a>]) -> Option<SrcRange> {
|
||||
let range = ttv.first()?.sr.range.start..ttv.last().unwrap().sr.range.end;
|
||||
Some(SrcRange { path: ttv.first().unwrap().sr.path(), range })
|
||||
}
|
||||
|
||||
/// Pretty-print a token sequence
|
||||
pub async fn ttv_fmt<'a: 'b, 'b>(
|
||||
ttv: impl IntoIterator<Item = &'b TokTree<impl ExprRepr + 'a, impl ExtraTok + 'a>>,
|
||||
c: &(impl FmtCtx + ?Sized),
|
||||
) -> FmtUnit {
|
||||
FmtUnit::sequence("", " ", "", None, join_all(ttv.into_iter().map(|t| t.print(c))).await)
|
||||
FmtUnit::sequence("", " ", "", true, join_all(ttv.into_iter().map(|t| t.print(c))).await)
|
||||
}
|
||||
|
||||
/// Indent a string by two spaces
|
||||
pub fn indent(s: &str) -> String { s.replace("\n", "\n ") }
|
||||
|
||||
Reference in New Issue
Block a user