task_local context over context objects

- interner impls logically separate from API in orchid-base (default host interner still in base for testing)
- error reporting, logging, and a variety of other features passed down via context in extension, not yet in host to maintain library-ish profile, should consider options
- no global spawn mechanic, the host has a spawn function but extensions only get a stash for enqueuing async work in sync callbacks which is then explicitly, manually, and with strict order popped and awaited
- still deadlocks nondeterministically for some ungodly reason
This commit is contained in:
2026-01-01 14:54:29 +00:00
parent 06debb3636
commit 32d6237dc5
92 changed files with 2507 additions and 2223 deletions

6
Cargo.lock generated
View File

@@ -1011,6 +1011,7 @@ dependencies = [
"orchid-api-traits",
"ordered-float",
"test_executors 0.3.5",
"unsync-pipe",
]
[[package]]
@@ -1028,7 +1029,6 @@ dependencies = [
name = "orchid-api-traits"
version = "0.1.0"
dependencies = [
"async-fn-stream",
"futures",
"itertools",
"never",
@@ -1056,8 +1056,8 @@ dependencies = [
"ordered-float",
"regex",
"rust-embed",
"some_executor",
"substack",
"task-local",
"test_executors 0.4.0",
"trait-set",
"unsync-pipe",
@@ -1069,6 +1069,7 @@ version = "0.1.0"
dependencies = [
"async-fn-stream",
"async-once-cell",
"bound",
"derive_destructure",
"dyn-clone",
"futures",
@@ -1087,7 +1088,6 @@ dependencies = [
"orchid-base",
"ordered-float",
"pastey",
"some_executor",
"substack",
"task-local",
"tokio",

View File

@@ -14,8 +14,8 @@ pub fn derive(input: TokenStream) -> TokenStream {
impl #impl_generics orchid_api_traits::Decode for #name #ty_generics #where_clause {
async fn decode<R: orchid_api_traits::AsyncRead + ?Sized>(
mut read: std::pin::Pin<&mut R>
) -> Self {
#decode
) -> std::io::Result<Self> {
Ok(#decode)
}
}
};
@@ -30,7 +30,7 @@ fn decode_fields(fields: &syn::Fields) -> pm2::TokenStream {
let syn::Field { ty, ident, .. } = &f;
quote! {
#ident : (Box::pin(< #ty as orchid_api_traits::Decode>::decode(read.as_mut()))
as std::pin::Pin<Box<dyn std::future::Future<Output = _>>>).await
as std::pin::Pin<Box<dyn std::future::Future<Output = std::io::Result<_>>>>).await?
}
});
quote! { { #( #exprs, )* } }
@@ -40,7 +40,7 @@ fn decode_fields(fields: &syn::Fields) -> pm2::TokenStream {
let ty = &field.ty;
quote! {
(Box::pin(< #ty as orchid_api_traits::Decode>::decode(read.as_mut()))
as std::pin::Pin<Box<dyn std::future::Future<Output = _>>>).await,
as std::pin::Pin<Box<dyn std::future::Future<Output = std::io::Result<_>>>>).await?,
}
});
quote! { ( #( #exprs )* ) }
@@ -62,7 +62,7 @@ fn decode_body(data: &syn::Data) -> proc_macro2::TokenStream {
quote! { #id => Self::#ident #fields, }
});
quote! {
match <u8 as orchid_api_traits::Decode>::decode(read.as_mut()).await {
match <u8 as orchid_api_traits::Decode>::decode(read.as_mut()).await? {
#(#opts)*
x => panic!("Unrecognized enum kind {x}")
}

View File

@@ -17,8 +17,9 @@ pub fn derive(input: TokenStream) -> TokenStream {
async fn encode<W: orchid_api_traits::AsyncWrite + ?Sized>(
&self,
mut write: std::pin::Pin<&mut W>
) {
#encode
) -> std::io::Result<()> {
#encode;
Ok(())
}
}
};
@@ -43,7 +44,7 @@ fn encode_body(data: &syn::Data) -> Option<pm2::TokenStream> {
quote! {
Self::#ident #dest => {
(Box::pin((#i as u8).encode(write.as_mut()))
as std::pin::Pin<Box<dyn std::future::Future<Output = _>>>).await;
as std::pin::Pin<Box<dyn std::future::Future<Output = std::io::Result<()>>>>).await?;
#body
}
}
@@ -61,7 +62,7 @@ fn encode_body(data: &syn::Data) -> Option<pm2::TokenStream> {
fn encode_names<T: ToTokens>(names: impl Iterator<Item = T>) -> pm2::TokenStream {
quote! { #(
(Box::pin(#names .encode(write.as_mut()))
as std::pin::Pin<Box<dyn std::future::Future<Output = _>>>).await;
as std::pin::Pin<Box<dyn std::future::Future<Output = std::io::Result<()>>>>).await?;
)* }
}

View File

@@ -6,7 +6,6 @@ edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
futures = { version = "0.3.31", features = ["std"], default-features = false }
itertools = "0.14.0"
never = "0.1.0"

View File

@@ -1,33 +1,44 @@
use std::collections::HashMap;
use std::future::Future;
use std::hash::Hash;
use std::io;
use std::num::NonZero;
use std::ops::{Range, RangeInclusive};
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
use async_fn_stream::stream;
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, StreamExt};
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use never::Never;
use ordered_float::NotNan;
use crate::encode_enum;
use crate::{decode_err, decode_err_for, encode_enum, spin_on};
pub trait Decode: 'static {
pub trait Decode: 'static + Sized {
/// Decode an instance from the beginning of the buffer. Return the decoded
/// data and the remaining buffer.
fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> impl Future<Output = Self> + '_;
fn decode<R: AsyncRead + ?Sized>(
read: Pin<&mut R>,
) -> impl Future<Output = io::Result<Self>> + '_;
fn decode_slice(slc: &mut &[u8]) -> Self {
spin_on(Self::decode(Pin::new(slc) as Pin<&mut _>)).expect("Decode from slice cannot fail")
}
}
pub trait Encode {
/// Append an instance of the struct to the buffer
fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> impl Future<Output = ()>;
fn encode<W: AsyncWrite + ?Sized>(
&self,
write: Pin<&mut W>,
) -> impl Future<Output = io::Result<()>>;
fn encode_vec(&self, vec: &mut Vec<u8>) {
spin_on(self.encode(Pin::new(vec) as Pin<&mut _>)).expect("Encode to vector cannot fail")
}
}
pub trait Coding: Encode + Decode + Clone {
fn get_decoder<T: 'static, F: Future<Output = T> + 'static>(
map: impl Fn(Self) -> F + Clone + 'static,
) -> impl AsyncFn(Pin<&mut dyn AsyncRead>) -> T {
async move |r| map(Self::decode(r).await).await
fn get_decoder<T: 'static>(
map: impl AsyncFn(Self) -> T + Clone + 'static,
) -> impl AsyncFn(Pin<&mut dyn AsyncRead>) -> io::Result<T> {
async move |r| Ok(map(Self::decode(r).await?).await)
}
}
impl<T: Encode + Decode + Clone> Coding for T {}
@@ -35,15 +46,15 @@ impl<T: Encode + Decode + Clone> Coding for T {}
macro_rules! num_impl {
($number:ty) => {
impl Decode for $number {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
let mut bytes = [0u8; (<$number>::BITS / 8) as usize];
read.read_exact(&mut bytes).await.unwrap();
<$number>::from_be_bytes(bytes)
read.read_exact(&mut bytes).await?;
Ok(<$number>::from_be_bytes(bytes))
}
}
impl Encode for $number {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
write.write_all(&self.to_be_bytes()).await.expect("Could not write number")
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
write.write_all(&self.to_be_bytes()).await
}
}
};
@@ -62,12 +73,12 @@ num_impl!(i8);
macro_rules! nonzero_impl {
($name:ty) => {
impl Decode for NonZero<$name> {
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> Self {
Self::new(<$name as Decode>::decode(read).await).unwrap()
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> io::Result<Self> {
Self::new(<$name as Decode>::decode(read).await?).ok_or_else(decode_err)
}
}
impl Encode for NonZero<$name> {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
self.get().encode(write).await
}
}
@@ -86,22 +97,22 @@ nonzero_impl!(i64);
nonzero_impl!(i128);
impl<T: Encode + ?Sized> Encode for &T {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
(**self).encode(write).await
}
}
macro_rules! float_impl {
($t:ty, $size:expr) => {
impl Decode for NotNan<$t> {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
let mut bytes = [0u8; $size];
read.read_exact(&mut bytes).await.unwrap();
NotNan::new(<$t>::from_be_bytes(bytes)).expect("Float was NaN")
read.read_exact(&mut bytes).await?;
NotNan::new(<$t>::from_be_bytes(bytes)).map_err(|_| decode_err())
}
}
impl Encode for NotNan<$t> {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
write.write_all(&self.as_ref().to_be_bytes()).await.expect("Could not write number")
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
write.write_all(&self.as_ref().to_be_bytes()).await
}
}
};
@@ -111,78 +122,77 @@ float_impl!(f64, 8);
float_impl!(f32, 4);
impl Decode for String {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
let len: usize = u64::decode(read.as_mut()).await.try_into().unwrap();
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
let len: usize = u64::decode(read.as_mut()).await?.try_into().map_err(decode_err_for)?;
let mut data = vec![0u8; len];
read.read_exact(&mut data).await.unwrap();
std::str::from_utf8(&data).expect("String invalid UTF-8").to_owned()
read.read_exact(&mut data).await?;
Ok(std::str::from_utf8(&data).map_err(decode_err_for)?.to_owned())
}
}
impl Encode for String {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await;
write.write_all(self.as_bytes()).await.unwrap()
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
u64::try_from(self.len()).map_err(decode_err_for)?.encode(write.as_mut()).await?;
write.write_all(self.as_bytes()).await
}
}
impl Encode for str {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await;
write.write_all(self.as_bytes()).await.unwrap()
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
u64::try_from(self.len()).map_err(decode_err_for)?.encode(write.as_mut()).await?;
write.write_all(self.as_bytes()).await
}
}
impl<T: Decode> Decode for Vec<T> {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
let len = u64::decode(read.as_mut()).await;
stream(async |mut cx| {
for _ in 0..len {
cx.emit(T::decode(read.as_mut()).await).await
}
})
.collect()
.await
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
let len = u64::decode(read.as_mut()).await?;
let mut values = Vec::with_capacity(len.try_into().map_err(decode_err_for)?);
for _ in 0..len {
values.push(T::decode(read.as_mut()).await?);
}
Ok(values)
}
}
impl<T: Encode> Encode for Vec<T> {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
self.as_slice().encode(write).await
}
}
impl<T: Encode> Encode for [T] {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await;
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await?;
for t in self.iter() {
t.encode(write.as_mut()).await
t.encode(write.as_mut()).await?
}
Ok(())
}
}
impl<T: Decode> Decode for Option<T> {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
match u8::decode(read.as_mut()).await {
0 => None,
1 => Some(T::decode(read).await),
x => panic!("{x} is not a valid option value"),
}
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
Ok(match bool::decode(read.as_mut()).await? {
false => None,
true => Some(T::decode(read).await?),
})
}
}
impl<T: Encode> Encode for Option<T> {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
let t = if let Some(t) = self { t } else { return 0u8.encode(write.as_mut()).await };
1u8.encode(write.as_mut()).await;
t.encode(write).await;
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
self.is_some().encode(write.as_mut()).await?;
if let Some(t) = self {
t.encode(write).await?
}
Ok(())
}
}
impl<T: Decode, E: Decode> Decode for Result<T, E> {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
match u8::decode(read.as_mut()).await {
0 => Self::Ok(T::decode(read).await),
1 => Self::Err(E::decode(read).await),
x => panic!("Invalid Result tag {x}"),
}
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
Ok(match bool::decode(read.as_mut()).await? {
false => Self::Ok(T::decode(read).await?),
true => Self::Err(E::decode(read).await?),
})
}
}
impl<T: Encode, E: Encode> Encode for Result<T, E> {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
match self {
Ok(t) => encode_enum(write, 0, |w| t.encode(w)).await,
Err(e) => encode_enum(write, 1, |w| e.encode(w)).await,
@@ -190,36 +200,37 @@ impl<T: Encode, E: Encode> Encode for Result<T, E> {
}
}
impl<K: Decode + Eq + Hash, V: Decode> Decode for HashMap<K, V> {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
let len = u64::decode(read.as_mut()).await;
stream(async |mut cx| {
for _ in 0..len {
cx.emit(<(K, V)>::decode(read.as_mut()).await).await
}
})
.collect()
.await
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
let len = u64::decode(read.as_mut()).await?;
let mut map = HashMap::with_capacity(len.try_into().map_err(decode_err_for)?);
for _ in 0..len {
map.insert(K::decode(read.as_mut()).await?, V::decode(read.as_mut()).await?);
}
Ok(map)
}
}
impl<K: Encode + Eq + Hash, V: Encode> Encode for HashMap<K, V> {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await;
for pair in self.iter() {
pair.encode(write.as_mut()).await
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await?;
for (key, value) in self.iter() {
key.encode(write.as_mut()).await?;
value.encode(write.as_mut()).await?;
}
Ok(())
}
}
macro_rules! tuple {
(($($t:ident)*) ($($T:ident)*)) => {
impl<$($T: Decode),*> Decode for ($($T,)*) {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
($($T::decode(read.as_mut()).await,)*)
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
Ok(($($T::decode(read.as_mut()).await?,)*))
}
}
impl<$($T: Encode),*> Encode for ($($T,)*) {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
let ($($t,)*) = self;
$( $t.encode(write.as_mut()).await; )*
$( $t.encode(write.as_mut()).await?; )*
Ok(())
}
}
};
@@ -243,63 +254,67 @@ tuple!((t u v x y z a b c d e f g h i) (T U V X Y Z A B C D E F G H I));
tuple!((t u v x y z a b c d e f g h i j) (T U V X Y Z A B C D E F G H I J)); // 16
impl Decode for () {
async fn decode<R: AsyncRead + ?Sized>(_: Pin<&mut R>) -> Self {}
async fn decode<R: AsyncRead + ?Sized>(_: Pin<&mut R>) -> io::Result<Self> { Ok(()) }
}
impl Encode for () {
async fn encode<W: AsyncWrite + ?Sized>(&self, _: Pin<&mut W>) {}
async fn encode<W: AsyncWrite + ?Sized>(&self, _: Pin<&mut W>) -> io::Result<()> { Ok(()) }
}
impl Decode for Never {
async fn decode<R: AsyncRead + ?Sized>(_: Pin<&mut R>) -> Self {
async fn decode<R: AsyncRead + ?Sized>(_: Pin<&mut R>) -> io::Result<Self> {
unreachable!("A value of Never cannot exist so it can't have been serialized");
}
}
impl Encode for Never {
async fn encode<W: AsyncWrite + ?Sized>(&self, _: Pin<&mut W>) { match *self {} }
async fn encode<W: AsyncWrite + ?Sized>(&self, _: Pin<&mut W>) -> io::Result<()> {
match *self {}
}
}
impl Decode for bool {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
let mut buf = [0];
read.read_exact(&mut buf).await.unwrap();
buf[0] != 0
read.read_exact(&mut buf).await?;
Ok(buf[0] != 0)
}
}
impl Encode for bool {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
write.write_all(&[if *self { 0xffu8 } else { 0u8 }]).await.unwrap()
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
write.write_all(&[if *self { 0xffu8 } else { 0u8 }]).await
}
}
impl<T: Decode, const N: usize> Decode for [T; N] {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
let v = stream(async |mut cx| {
for _ in 0..N {
cx.emit(T::decode(read.as_mut()).await).await
}
})
.collect::<Vec<_>>()
.await;
v.try_into().unwrap_or_else(|_| unreachable!("The length of this stream is statically known"))
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
let mut v = Vec::with_capacity(N);
for _ in 0..N {
v.push(T::decode(read.as_mut()).await?);
}
match v.try_into() {
Err(_) => unreachable!("The length of this stream is statically known"),
Ok(arr) => Ok(arr),
}
}
}
impl<T: Encode, const N: usize> Encode for [T; N] {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
for t in self.iter() {
t.encode(write.as_mut()).await
t.encode(write.as_mut()).await?
}
Ok(())
}
}
macro_rules! two_end_range {
($this:ident, $name:tt, $op:tt, $start:expr, $end:expr) => {
impl<T: Decode> Decode for $name<T> {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
T::decode(read.as_mut()).await $op T::decode(read).await
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
Ok(T::decode(read.as_mut()).await? $op T::decode(read).await?)
}
}
impl<T: Encode> Encode for $name<T> {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
let $this = self;
($start).encode(write.as_mut()).await;
($end).encode(write).await;
($start).encode(write.as_mut()).await?;
($end).encode(write).await?;
Ok(())
}
}
}
@@ -311,12 +326,12 @@ two_end_range!(x, RangeInclusive, ..=, x.start(), x.end());
macro_rules! smart_ptr {
($name:tt) => {
impl<T: Decode> Decode for $name<T> {
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> Self {
$name::new(T::decode(read).await)
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> io::Result<Self> {
Ok($name::new(T::decode(read).await?))
}
}
impl<T: Encode> Encode for $name<T> {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
(**self).encode(write).await
}
}
@@ -328,12 +343,12 @@ smart_ptr!(Rc);
smart_ptr!(Box);
impl Decode for char {
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> Self {
char::from_u32(u32::decode(read).await).unwrap()
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> io::Result<Self> {
char::from_u32(u32::decode(read).await?).ok_or_else(decode_err)
}
}
impl Encode for char {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
(*self as u32).encode(write).await
}
}

View File

@@ -1,24 +1,24 @@
use std::future::Future;
use std::pin::Pin;
use std::error::Error;
use std::io;
use std::pin::{Pin, pin};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll, Wake};
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use futures::{AsyncRead, AsyncReadExt, AsyncWrite};
use itertools::{Chunk, Itertools};
use crate::Encode;
pub async fn encode_enum<'a, W: AsyncWrite + ?Sized, F: Future<Output = ()>>(
pub async fn encode_enum<'a, W: AsyncWrite + ?Sized>(
mut write: Pin<&'a mut W>,
id: u8,
f: impl FnOnce(Pin<&'a mut W>) -> F,
) {
id.encode(write.as_mut()).await;
f: impl AsyncFnOnce(Pin<&'a mut W>) -> io::Result<()>,
) -> io::Result<()> {
id.encode(write.as_mut()).await?;
f(write).await
}
pub async fn write_exact<W: AsyncWrite + ?Sized>(mut write: Pin<&mut W>, bytes: &'static [u8]) {
write.write_all(bytes).await.expect("Failed to write exact bytes")
}
pub fn print_bytes(b: &[u8]) -> String {
(b.iter().map(|b| format!("{b:02x}")))
.chunks(4)
@@ -27,16 +27,52 @@ pub fn print_bytes(b: &[u8]) -> String {
.join(" ")
}
pub async fn read_exact<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>, bytes: &'static [u8]) {
pub async fn read_exact<R: AsyncRead + ?Sized>(
mut read: Pin<&mut R>,
bytes: &'static [u8],
) -> io::Result<()> {
let mut data = vec![0u8; bytes.len()];
read.read_exact(&mut data).await.expect("Failed to read bytes");
if data != bytes {
panic!("Wrong bytes!\nExpected: {}\nFound: {}", print_bytes(bytes), print_bytes(&data));
read.read_exact(&mut data).await?;
if data == bytes {
Ok(())
} else {
let msg =
format!("Wrong bytes!\nExpected: {}\nFound: {}", print_bytes(bytes), print_bytes(&data));
Err(io::Error::new(io::ErrorKind::InvalidData, msg))
}
}
pub async fn enc_vec(enc: &impl Encode) -> Vec<u8> {
pub fn enc_vec(enc: &impl Encode) -> Vec<u8> {
let mut vec = Vec::new();
enc.encode(Pin::new(&mut vec)).await;
enc.encode_vec(&mut vec);
vec
}
/// Raises a bool flag when called
struct FlagWaker(AtomicBool);
impl Wake for FlagWaker {
fn wake(self: Arc<Self>) { self.0.store(true, Ordering::Relaxed) }
}
pub fn spin_on<F: Future>(fut: F) -> F::Output {
let flag = AtomicBool::new(false);
let flag_waker = Arc::new(FlagWaker(flag));
let mut future = pin!(fut);
loop {
let waker = flag_waker.clone().into();
let mut ctx = Context::from_waker(&waker);
match future.as_mut().poll(&mut ctx) {
// ideally the future should return synchronously
Poll::Ready(res) => break res,
// poorly written futures may yield and immediately wake
Poll::Pending if flag_waker.0.load(Ordering::Relaxed) => (),
// there is no external event to wait for, this has to be a deadlock
Poll::Pending => panic!("Future inside spin_on cannot block"),
};
}
}
pub fn decode_err() -> io::Error { io::Error::new(io::ErrorKind::InvalidData, "Unexpected zero") }
pub fn decode_err_for(e: impl Error) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, e.to_string())
}

View File

@@ -9,7 +9,7 @@ pub trait Request: fmt::Debug + Sized + 'static {
type Response: fmt::Debug + Coding + 'static;
}
pub async fn respond<R: Request>(_: &R, rep: R::Response) -> Vec<u8> { enc_vec(&rep).await }
pub fn respond<R: Request>(_: &R, rep: R::Response) -> Vec<u8> { enc_vec(&rep) }
pub trait Channel: 'static {
type Req: Coding + Sized + 'static;

View File

@@ -11,6 +11,7 @@ orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
futures = { version = "0.3.31", features = ["std"], default-features = false }
itertools = "0.14.0"
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
[dev-dependencies]
test_executors = "0.3.5"

67
orchid-api/src/binary.rs Normal file
View File

@@ -0,0 +1,67 @@
//! # Binary extension definition
//!
//! A binary extension is a DLL / shared object / dylib with a symbol called
//! `orchid_extension_main` which accepts a single argument of type [ExtCtx].
//! Once that is received, communication continuees through the channel with the
//! same protocol outlined in [crate::proto]
use unsync_pipe::{Reader, Writer};
/// !Send !Sync owned waker
#[repr(C)]
pub struct OwnedWakerVT {
data: *const (),
/// `self`
drop: extern "C" fn(*const ()),
/// `&self`
wake: extern "C" fn(*const ()),
}
/// !Send !Sync, equivalent to `&mut Context<'a>`, hence no `drop`.
/// When received in [FutureVT::poll], it must not outlive the call.
#[repr(C)]
pub struct FutureContextVT {
data: *const (),
/// `&self`
waker: extern "C" fn(*const ()) -> OwnedWakerVT,
}
/// ABI-stable `Poll<()>`
#[repr(C)]
pub enum UnitPoll {
Pending,
Ready,
}
/// ABI-stable `Pin<Box<dyn Future<Output = ()>>>`
#[repr(C)]
pub struct FutureVT {
data: *const (),
/// `self`
drop: extern "C" fn(*const ()),
/// `&mut self` Equivalent to [Future::poll]
poll: extern "C" fn(*const (), FutureContextVT) -> UnitPoll,
}
/// Owned extension context.
///
/// When an extension starts, this is passed to
#[repr(C)]
pub struct ExtensionContext {
data: *const (),
/// `self`
drop: extern "C" fn(*const ()),
/// `self` upgrade to a later version of this struct. May only be called if
/// none of the other elements have been used yet. If a newer version isn't
/// supported, the server must return null, otherwise the the return value is
/// a pointer to the immediate next version of this struct
next: extern "C" fn(*const ()) -> *const (),
/// `&self` Add a future to this extension's task
spawn: extern "C" fn(*const (), FutureVT),
/// serialized [crate::HostExtChannel]
input: Reader,
/// serialized [crate::ExtHostChannel]
output: Writer,
/// UTF-8 log stream directly to log service.
log: Writer,
}

View File

@@ -1,3 +1,4 @@
pub mod binary;
mod lexer;
pub use lexer::*;
mod format;

View File

@@ -22,51 +22,54 @@
//! be preserved. Toolkits must ensure that the client code is able to observe
//! the ordering of messages.
use std::io;
use std::pin::Pin;
use futures::{AsyncRead, AsyncWrite};
use futures::{AsyncRead, AsyncWrite, AsyncWriteExt};
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::{Channel, Decode, Encode, MsgSet, Request, read_exact, write_exact};
use orchid_api_traits::{Channel, Decode, Encode, MsgSet, Request, read_exact};
use crate::{Sweeped, atom, expr, interner, lexer, logging, parser, system, tree};
static HOST_INTRO: &[u8] = b"Orchid host, binary API v0\n";
#[derive(Clone, Debug)]
pub struct HostHeader {
pub log_strategy: logging::LogStrategy,
pub msg_logs: logging::LogStrategy,
}
impl Decode for HostHeader {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
read_exact(read.as_mut(), HOST_INTRO).await;
Self {
log_strategy: logging::LogStrategy::decode(read.as_mut()).await,
msg_logs: logging::LogStrategy::decode(read.as_mut()).await,
}
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
read_exact(read.as_mut(), HOST_INTRO).await?;
Ok(Self {
log_strategy: logging::LogStrategy::decode(read.as_mut()).await?,
msg_logs: logging::LogStrategy::decode(read.as_mut()).await?,
})
}
}
impl Encode for HostHeader {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
write_exact(write.as_mut(), HOST_INTRO).await;
self.log_strategy.encode(write.as_mut()).await;
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
write.write_all(HOST_INTRO).await?;
self.log_strategy.encode(write.as_mut()).await?;
self.msg_logs.encode(write.as_mut()).await
}
}
static EXT_INTRO: &[u8] = b"Orchid extension, binary API v0\n";
#[derive(Clone, Debug)]
pub struct ExtensionHeader {
pub name: String,
pub systems: Vec<system::SystemDecl>,
}
impl Decode for ExtensionHeader {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
read_exact(read.as_mut(), EXT_INTRO).await;
Self { name: String::decode(read.as_mut()).await, systems: Vec::decode(read).await }
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
read_exact(read.as_mut(), EXT_INTRO).await?;
Ok(Self { name: String::decode(read.as_mut()).await?, systems: Vec::decode(read).await? })
}
}
impl Encode for ExtensionHeader {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
write_exact(write.as_mut(), EXT_INTRO).await;
self.name.encode(write.as_mut()).await;
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
write.write_all(EXT_INTRO).await?;
self.name.encode(write.as_mut()).await?;
self.systems.encode(write).await
}
}
@@ -169,9 +172,9 @@ mod tests {
log_strategy: logging::LogStrategy::File("SomeFile".to_string()),
msg_logs: logging::LogStrategy::File("SomeFile".to_string()),
};
let mut enc = &enc_vec(&hh).await[..];
let mut enc = &enc_vec(&hh)[..];
eprintln!("Encoded to {enc:?}");
HostHeader::decode(Pin::new(&mut enc)).await;
HostHeader::decode(Pin::new(&mut enc)).await.unwrap();
assert_eq!(enc, []);
})
}
@@ -188,9 +191,9 @@ mod tests {
priority: NotNan::new(1f64).unwrap(),
}],
};
let mut enc = &enc_vec(&eh).await[..];
let mut enc = &enc_vec(&eh)[..];
eprintln!("Encoded to {enc:?}");
ExtensionHeader::decode(Pin::new(&mut enc)).await;
ExtensionHeader::decode(Pin::new(&mut enc)).await.unwrap();
assert_eq!(enc, [])
})
}

View File

@@ -24,9 +24,9 @@ orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
ordered-float = "5.0.0"
regex = "1.11.2"
rust-embed = "8.7.2"
some_executor = "0.6.1"
substack = "1.1.1"
trait-set = "0.3.0"
task-local = "0.1.0"
[dev-dependencies]
futures = "0.3.31"

View File

@@ -1,34 +0,0 @@
use std::ops::Deref;
use std::rc::Rc;
use futures::future::LocalBoxFuture;
use crate::api;
pub type Spawner = Rc<dyn Fn(LocalBoxFuture<'static, ()>)>;
/// The 3 primary contact points with an extension are
/// - send a message
/// - wait for a message to arrive
/// - wait for the extension to stop after exit (this is the implicit Drop)
///
/// There are no ordering guarantees about these
pub trait ExtPort {
#[must_use]
fn send<'a>(&'a self, msg: &'a [u8]) -> LocalBoxFuture<'a, ()>;
#[must_use]
fn recv(&self) -> LocalBoxFuture<'_, Option<Vec<u8>>>;
}
pub struct ExtInit {
pub header: api::ExtensionHeader,
pub port: Box<dyn ExtPort>,
}
impl ExtInit {
pub async fn send(&self, msg: &[u8]) { self.port.send(msg).await }
pub async fn recv(&self) -> Option<Vec<u8>> { self.port.recv().await }
}
impl Deref for ExtInit {
type Target = api::ExtensionHeader;
fn deref(&self) -> &Self::Target { &self.header }
}

View File

@@ -5,9 +5,10 @@ use std::ops::Add;
use std::rc::Rc;
use std::sync::Arc;
use futures::FutureExt;
use futures::future::join_all;
use itertools::Itertools;
use some_executor::task_local;
use task_local::task_local;
use crate::api;
use crate::interner::{IStr, es, is};
@@ -237,7 +238,19 @@ task_local! {
static REPORTER: Reporter;
}
pub async fn with_reporter<T>(fut: impl Future<Output = OrcRes<T>>) -> OrcRes<T> {
/// Run the future with a new reporter, and return all errors reported within.
///
/// If your future returns [OrcRes], see [try_with_reporter]
pub async fn with_reporter<T>(fut: impl Future<Output = T>) -> OrcRes<T> {
try_with_reporter(fut.map(Ok)).await
}
/// Run the future with a new reporter, and return all errors either returned or
/// reported by it
///
/// If your future may report errors but always returns an approximate value,
/// see [with_reporter]
pub async fn try_with_reporter<T>(fut: impl Future<Output = OrcRes<T>>) -> OrcRes<T> {
let rep = Reporter::default();
let res = REPORTER.scope(rep.clone(), fut).await;
let errors = rep.errors.take();
@@ -249,9 +262,8 @@ pub async fn with_reporter<T>(fut: impl Future<Output = OrcRes<T>>) -> OrcRes<T>
}
pub async fn is_erroring() -> bool {
REPORTER.with(|r| {
!r.expect("Sidechannel errors must be caught by a reporter").errors.borrow().is_empty()
})
(REPORTER.try_with(|r| !r.errors.borrow().is_empty()))
.expect("Sidechannel errors must be caught by a reporter")
}
/// Report an error that is fatal and prevents a correct output, but
@@ -259,11 +271,10 @@ pub async fn is_erroring() -> bool {
/// This can be used for
pub fn report(e: impl Into<OrcErrv>) {
let errv = e.into();
REPORTER.with(|r| match r {
Some(r) => r.errors.borrow_mut().extend(errv),
None => panic!(
REPORTER.try_with(|r| r.errors.borrow_mut().extend(errv.clone())).unwrap_or_else(|_| {
panic!(
"Unhandled error! Sidechannel errors must be caught by an enclosing call to with_reporter.\n\
Error: {errv}",
),
Error: {errv}"
)
})
}

View File

@@ -304,6 +304,7 @@ pub async fn take_first_fmt(v: &(impl Format + ?Sized)) -> String {
take_first(&v.print(&FmtCtxImpl { _foo: PhantomData }).await, false)
}
#[derive(Default)]
pub struct FmtCtxImpl<'a> {
_foo: PhantomData<&'a ()>,
}
@@ -331,8 +332,8 @@ impl Format for Never {
/// Format with default strategy. Currently equal to [take_first_fmt]
pub async fn fmt(v: &(impl Format + ?Sized)) -> String { take_first_fmt(v).await }
/// Format a sequence with default strategy. Currently equal to [take_first_fmt]
pub async fn fmt_v<F: Format + ?Sized, R: Borrow<F>>(
v: impl IntoIterator<Item = R>,
pub async fn fmt_v<F: Format + ?Sized>(
v: impl IntoIterator<Item: Borrow<F>>,
) -> impl Iterator<Item = String> {
join_all(v.into_iter().map(|f| async move { take_first_fmt(f.borrow()).await })).await.into_iter()
}

View File

@@ -6,12 +6,16 @@ use std::rc::Rc;
use std::{fmt, hash};
use futures::future::LocalBoxFuture;
use some_executor::task_local;
use task_local::task_local;
use crate::api;
pub trait IStrHandle: AsRef<str> {}
pub trait IStrvHandle: AsRef<[IStr]> {}
pub trait IStrHandle: AsRef<str> {
fn rc(&self) -> Rc<String>;
}
pub trait IStrvHandle: AsRef<[IStr]> {
fn rc(&self) -> Rc<Vec<IStr>>;
}
#[derive(Clone)]
pub struct IStr(pub api::TStr, pub Rc<dyn IStrHandle>);
@@ -22,6 +26,7 @@ impl IStr {
/// 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.
pub fn to_api(&self) -> api::TStr { self.0 }
pub fn rc(&self) -> Rc<String> { self.1.rc() }
}
impl Deref for IStr {
type Target = str;
@@ -49,6 +54,7 @@ impl IStrv {
/// 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.
pub fn to_api(&self) -> api::TStrv { self.0 }
pub fn rc(&self) -> Rc<Vec<IStr>> { self.1.rc() }
}
impl Deref for IStrv {
type Target = [IStr];
@@ -79,10 +85,10 @@ impl Debug for IStrv {
}
pub trait InternerSrv {
fn is(&self, v: &str) -> LocalBoxFuture<'static, IStr>;
fn es(&self, t: api::TStr) -> LocalBoxFuture<'static, IStr>;
fn iv(&self, v: &[IStr]) -> LocalBoxFuture<'static, IStrv>;
fn ev(&self, t: api::TStrv) -> LocalBoxFuture<'static, IStrv>;
fn is<'a>(&'a self, v: &'a str) -> LocalBoxFuture<'a, IStr>;
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr>;
fn iv<'a>(&'a self, v: &'a [IStr]) -> LocalBoxFuture<'a, IStrv>;
fn ev(&self, t: api::TStrv) -> LocalBoxFuture<'_, IStrv>;
}
task_local! {
@@ -94,10 +100,283 @@ pub async fn with_interner<F: Future>(val: Rc<dyn InternerSrv>, fut: F) -> F::Ou
}
fn get_interner() -> Rc<dyn InternerSrv> {
INTERNER.with(|i| i.expect("Interner not initialized").clone())
INTERNER.try_with(|i| i.clone()).expect("Interner not initialized")
}
pub async fn is(v: &str) -> IStr { get_interner().is(v).await }
pub async fn iv(v: &[IStr]) -> IStrv { get_interner().iv(v).await }
pub async fn es(v: api::TStr) -> IStr { get_interner().es(v).await }
pub async fn ev(v: api::TStrv) -> IStrv { get_interner().ev(v).await }
pub mod local_interner {
use std::borrow::Borrow;
use std::cell::RefCell;
use std::fmt::Debug;
use std::future;
use std::hash::{BuildHasher, Hash};
use std::num::NonZeroU64;
use std::rc::{Rc, Weak};
use futures::future::LocalBoxFuture;
use hashbrown::hash_table::{Entry, OccupiedEntry, VacantEntry};
use hashbrown::{DefaultHashBuilder, HashTable};
use orchid_api_traits::Coding;
use super::{IStr, IStrHandle, IStrv, IStrvHandle, InternerSrv};
use crate::api;
/// Associated types and methods for parallel concepts between scalar and
/// vector interning
pub trait InternableCard: 'static + Sized + Default + Debug {
/// API representation of an interner key
type Token: Clone + Copy + Debug + Hash + Eq + PartialOrd + Ord + Coding + 'static;
/// Owned version of interned value physically held by `'static` interner
/// and token
type Data: 'static + Borrow<Self::Borrow> + Eq + Hash + Debug;
/// Borrowed version of interned value placed in intern queries to avoid a
/// copy
type Borrow: ToOwned<Owned = Self::Data> + ?Sized + Eq + Hash + Debug;
/// Smart object handed out by the interner for storage and comparison in
/// third party code. [IStr] or [IStrv]
type Interned: Clone + Debug;
/// Create smart object from token for fast comparison and a handle for
/// everything else incl. virtual drop
fn new_interned(token: Self::Token, handle: Rc<Handle<Self>>) -> Self::Interned;
}
#[derive(Default, Debug)]
pub struct StrBranch;
impl InternableCard for StrBranch {
type Data = String;
type Token = api::TStr;
type Borrow = str;
type Interned = IStr;
fn new_interned(t: Self::Token, h: Rc<Handle<Self>>) -> Self::Interned { IStr(t, h) }
}
#[derive(Default, Debug)]
pub struct StrvBranch;
impl InternableCard for StrvBranch {
type Data = Vec<IStr>;
type Token = api::TStrv;
type Borrow = [IStr];
type Interned = IStrv;
fn new_interned(t: Self::Token, h: Rc<Handle<Self>>) -> Self::Interned { IStrv(t, h) }
}
/// Pairs interned data with its internment key
#[derive(Debug)]
struct Data<B: InternableCard> {
token: B::Token,
data: Rc<B::Data>,
}
impl<B: InternableCard> Clone for Data<B> {
fn clone(&self) -> Self { Self { token: self.token, data: self.data.clone() } }
}
/// Implementor for the trait objects held by [IStr] and [IStrv]
pub struct Handle<B: InternableCard> {
data: Data<B>,
parent: Weak<RefCell<IntData<B>>>,
}
impl IStrHandle for Handle<StrBranch> {
fn rc(&self) -> Rc<String> { self.data.data.clone() }
}
impl AsRef<str> for Handle<StrBranch> {
fn as_ref(&self) -> &str { self.data.data.as_ref().as_ref() }
}
impl IStrvHandle for Handle<StrvBranch> {
fn rc(&self) -> Rc<Vec<IStr>> { self.data.data.clone() }
}
impl AsRef<[IStr]> for Handle<StrvBranch> {
fn as_ref(&self) -> &[IStr] { self.data.data.as_ref().as_ref() }
}
impl<B: InternableCard> Drop for Handle<B> {
fn drop(&mut self) {
let Some(parent) = self.parent.upgrade() else { return };
if let Entry::Occupied(ent) =
parent.borrow_mut().entry_by_data(self.data.data.as_ref().borrow())
{
ent.remove();
}
if let Entry::Occupied(ent) = parent.borrow_mut().entry_by_tok(self.data.token) {
ent.remove();
}
}
}
/// 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.
handle: Weak<Handle<B>>,
/// Keys for indexing from either table
data: Data<B>,
}
/// Read data from an occupied entry in an interner. The equivalent insert
/// command is [insert]
fn read<B: InternableCard>(entry: OccupiedEntry<'_, Rec<B>>) -> B::Interned {
let hand = entry.get().handle.upgrade().expect("Found entry but handle already dropped");
B::new_interned(entry.get().data.token, hand)
}
/// Insert some data into an entry borrowed from this same interner.
/// The equivalent read command is [read]
fn insert<B: InternableCard>(entry: VacantEntry<'_, Rec<B>>, handle: Rc<Handle<B>>) {
entry.insert(Rec { data: handle.data.clone(), handle: Rc::downgrade(&handle) });
}
#[derive(Default)]
struct IntData<B: InternableCard> {
by_tok: HashTable<Rec<B>>,
by_data: HashTable<Rec<B>>,
hasher: DefaultHashBuilder,
}
impl<B: InternableCard> IntData<B> {
fn entry_by_data(&mut self, query: &B::Borrow) -> Entry<'_, Rec<B>> {
self.by_data.entry(
self.hasher.hash_one(query),
|rec| rec.data.data.as_ref().borrow() == query,
|rec| self.hasher.hash_one(rec.data.data.as_ref().borrow()),
)
}
fn entry_by_tok(&mut self, token: B::Token) -> Entry<'_, Rec<B>> {
self.by_tok.entry(
self.hasher.hash_one(token),
|rec| rec.data.token == token,
|rec| self.hasher.hash_one(rec.data.token),
)
}
}
/// Failing intern command that can be recovered if the value is found
/// elsewhere
pub struct InternError<'a, B: InternableCard> {
int: &'a Int<B>,
query: &'a B::Borrow,
}
impl<B: InternableCard> InternError<'_, B> {
/// If a racing write populates the entry, the continuation returns that
/// value and discards its argument
pub fn set_if_empty(self, token: B::Token) -> B::Interned {
let mut int_data = self.int.0.borrow_mut();
match int_data.entry_by_data(self.query) {
Entry::Occupied(ent) => read(ent),
Entry::Vacant(ent) => {
let hand = self.int.mk_handle(Data { token, data: Rc::new(self.query.to_owned()) });
insert(ent, hand.clone());
let Entry::Vacant(other_ent) = int_data.entry_by_tok(token) else {
panic!("Data and key tables out of sync")
};
insert(other_ent, hand.clone());
B::new_interned(token, hand)
},
}
}
}
impl<B: InternableCard> Debug for InternError<'_, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("InternEntry").field(&self.query).finish()
}
}
/// Failing extern command that can be recovered if the value is found
/// elsewhere
pub struct ExternError<'a, B: InternableCard> {
int: &'a Int<B>,
token: B::Token,
}
impl<B: InternableCard> ExternError<'_, B> {
/// If a racing write populates the entry, the continuation returns that
/// value and discards its argument
pub fn set_if_empty(&self, data: Rc<B::Data>) -> B::Interned {
let mut int_data = self.int.0.borrow_mut();
match int_data.entry_by_tok(self.token) {
Entry::Occupied(ent) => read(ent),
Entry::Vacant(ent) => {
let hand = self.int.mk_handle(Data { token: self.token, data: data.clone() });
insert(ent, hand.clone());
let Entry::Vacant(other_ent) = int_data.entry_by_data(data.as_ref().borrow()) else {
panic!("Data and key tables out of sync")
};
insert(other_ent, hand.clone());
B::new_interned(self.token, hand)
},
}
}
}
impl<B: InternableCard> Debug for ExternError<'_, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ExternEntry").field(&self.token).finish()
}
}
#[derive(Default)]
pub struct Int<B: InternableCard>(Rc<RefCell<IntData<B>>>);
impl<B: InternableCard> Int<B> {
fn mk_handle(&self, data: Data<B>) -> Rc<Handle<B>> {
Rc::new(Handle { data: data.clone(), parent: Rc::downgrade(&self.0.clone()) })
}
/// Look up by value, or yield to figure out its ID from elsewhere
pub fn i<'a>(&'a self, query: &'a B::Borrow) -> Result<B::Interned, InternError<'a, B>> {
if let Entry::Occupied(val) = self.0.borrow_mut().entry_by_data(query) {
return Ok(read(val));
}
Err(InternError { int: self, query })
}
/// Look up by key or yield to figure out its value from elsewhere
pub fn e(&self, token: B::Token) -> Result<B::Interned, ExternError<'_, B>> {
if let Entry::Occupied(ent) = self.0.borrow_mut().entry_by_tok(token) {
return Ok(read(ent));
}
Err(ExternError { int: self, token })
}
}
thread_local! {
static NEXT_ID: RefCell<u64> = 0.into();
}
fn with_new_id<T>(fun: impl FnOnce(NonZeroU64) -> T) -> T {
fun(
NonZeroU64::new(NEXT_ID.with_borrow_mut(|id| {
*id += 1;
*id
}))
.unwrap(),
)
}
#[derive(Default)]
struct LocalInterner {
str: Int<StrBranch>,
strv: Int<StrvBranch>,
}
impl InternerSrv for LocalInterner {
fn is<'a>(&'a self, v: &'a str) -> LocalBoxFuture<'a, IStr> {
match self.str.i(v) {
Ok(int) => Box::pin(future::ready(int)),
Err(e) => with_new_id(|id| Box::pin(future::ready(e.set_if_empty(api::TStr(id))))),
}
}
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr> {
Box::pin(future::ready(self.str.e(t).expect("Unrecognized token cannot be externed")))
}
fn iv<'a>(&'a self, v: &'a [IStr]) -> LocalBoxFuture<'a, IStrv> {
match self.strv.i(v) {
Ok(int) => Box::pin(future::ready(int)),
Err(e) => with_new_id(|id| Box::pin(future::ready(e.set_if_empty(api::TStrv(id))))),
}
}
fn ev(&self, t: orchid_api::TStrv) -> LocalBoxFuture<'_, IStrv> {
Box::pin(future::ready(self.strv.e(t).expect("Unrecognized token cannot be externed")))
}
}
/// Creates a basic thread-local interner for testing and root role.
pub fn local_interner() -> Rc<dyn InternerSrv> { Rc::<LocalInterner>::default() }
}

View File

@@ -3,7 +3,6 @@ use orchid_api as api;
pub mod box_cow;
pub mod boxed_iter;
pub mod builtin;
pub mod char_filter;
pub mod clone;
pub mod combine;
@@ -14,6 +13,7 @@ pub mod id_store;
pub mod interner;
pub mod iter_utils;
pub mod join;
mod localset;
pub mod location;
pub mod logging;
mod match_mapping;

View File

@@ -0,0 +1,48 @@
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::Pending => {
any_pending = true;
break;
},
Poll::Ready(None) => break,
Poll::Ready(Some(fut)) => this.pending.push_back(fut),
}
}
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(())) }
}
}

View File

@@ -4,6 +4,7 @@ use std::io::{Write, stderr};
pub use api::LogStrategy;
use itertools::Itertools;
use task_local::task_local;
use crate::api;
@@ -34,3 +35,13 @@ impl Logger {
}
}
}
task_local! {
static LOGGER: Logger;
}
pub async fn with_logger<F: Future>(logger: Logger, fut: F) -> F::Output {
LOGGER.scope(logger, fut).await
}
pub fn logger() -> Logger { LOGGER.try_with(|l| l.clone()).expect("Logger not set!") }

View File

@@ -6,7 +6,8 @@ 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![];
u32::try_from(msg.len()).unwrap().encode(Pin::new(&mut len_buf)).await;
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
@@ -15,7 +16,7 @@ pub async fn send_msg(mut write: Pin<&mut impl AsyncWrite>, msg: &[u8]) -> io::R
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 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)

View File

@@ -1,12 +1,11 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::{Pin, pin};
use std::rc::Rc;
use std::task::Poll;
use std::{io, mem};
use async_fn_stream::stream;
use async_fn_stream::try_stream;
use bound::Bound;
use derive_destructure::destructure;
use futures::channel::mpsc::{self, Receiver, Sender, channel};
@@ -14,23 +13,29 @@ use futures::channel::oneshot;
use futures::future::LocalBoxFuture;
use futures::lock::{Mutex, MutexGuard};
use futures::{
AsyncRead, AsyncWrite, AsyncWriteExt, SinkExt, Stream, StreamExt, stream, stream_select,
AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt, Stream, StreamExt, stream_select,
};
use hashbrown::HashMap;
use orchid_api_traits::{Channel, Decode, Encode, Request, UnderRoot};
use orchid_api_traits::{Decode, Encode, Request, UnderRoot};
use crate::localset::LocalSet;
#[must_use = "Receipts indicate that a required action has been performed within a function. \
Most likely this should be returned somewhere."]
pub struct Receipt<'a>(PhantomData<&'a mut ()>);
impl Receipt<'_> {
/// Only call this function from a custom implementation of [RepWriter]
pub fn _new() -> Self { Self(PhantomData) }
}
/// Write guard to outbound for the purpose of serializing a request. Only one
/// can exist at a time. Dropping this object should panic.
pub trait ReqWriter {
pub trait ReqWriter<'a> {
/// Access to the underlying channel. This may be buffered.
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
/// Finalize the request, release the outbound channel, then queue for the
/// reply on the inbound channel.
fn send(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn RepReader>>;
fn send(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepReader<'a> + 'a>>>;
}
/// Write guard to inbound for the purpose of deserializing a reply. While held,
@@ -40,49 +45,106 @@ pub trait ReqWriter {
/// synchronously, because the API isn't cancellation safe in general so it is a
/// programmer error in all cases to drop an object related to it without proper
/// cleanup.
pub trait RepReader {
pub trait RepReader<'a> {
/// Access to the underlying channel. The length of the message is inferred
/// from the number of bytes read so this must not be buffered.
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
/// Finish reading the request
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, ()>;
}
/// Write guard to outbound for the purpose of serializing a notification.
///
/// Dropping this object should panic for the same reason [RepReader] panics
pub trait MsgWriter {
pub trait MsgWriter<'a> {
/// Access to the underlying channel. This may be buffered.
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
/// Send the notification
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<()>>;
}
/// For initiating outbound requests and notifications
pub trait Client {
fn start_request(&self) -> LocalBoxFuture<'_, Box<dyn ReqWriter>>;
fn start_notif(&self) -> LocalBoxFuture<'_, Box<dyn MsgWriter>>;
fn start_request(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn ReqWriter<'_> + '_>>>;
fn start_notif(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn MsgWriter<'_> + '_>>>;
}
impl<T: Client + ?Sized> ClientExt for T {}
/// Extension trait with convenience methods that handle outbound request and
/// notif lifecycle and typing
#[allow(async_fn_in_trait)]
pub trait ClientExt<CH: Channel>: Client {
async fn request<T: Request + UnderRoot<Root = CH::Req>>(&self, t: T) -> T::Response {
let mut req = self.start_request().await;
t.into_root().encode(req.writer().as_mut()).await;
let mut rep = req.send().await;
pub trait ClientExt: Client {
async fn request<T: Request + UnderRoot<Root: Encode>>(&self, t: T) -> io::Result<T::Response> {
let mut req = self.start_request().await?;
t.into_root().encode(req.writer().as_mut()).await?;
let mut rep = req.send().await?;
let response = T::Response::decode(rep.reader()).await;
rep.finish().await;
response
}
async fn notify<T: UnderRoot<Root = CH::Notif>>(&self, t: T) {
let mut notif = self.start_notif().await;
t.into_root().encode(notif.writer().as_mut()).await;
notif.finish().await;
async fn notify<T: UnderRoot<Root: Encode>>(&self, t: T) -> io::Result<()> {
let mut notif = self.start_notif().await?;
t.into_root().encode(notif.writer().as_mut()).await?;
notif.finish().await?;
Ok(())
}
}
pub trait ReqReader<'a> {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>>;
}
impl<'a, T: ReqReader<'a> + ?Sized> ReqReaderExt<'a> for T {}
#[allow(async_fn_in_trait)]
pub trait ReqReaderExt<'a>: ReqReader<'a> {
async fn read_req<R: Decode>(&mut self) -> io::Result<R> { R::decode(self.reader()).await }
async fn reply<R: Request>(
self: Box<Self>,
req: impl Evidence<R>,
rep: &R::Response,
) -> io::Result<Receipt<'a>> {
self.finish().await.reply(req, rep).await
}
async fn start_reply(self: Box<Self>) -> io::Result<Box<dyn RepWriter<'a> + 'a>> {
self.finish().await.start_reply().await
}
}
pub trait ReqHandle<'a> {
fn start_reply(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>>;
}
impl<'a, T: ReqHandle<'a> + ?Sized> ReqHandleExt<'a> for T {}
#[allow(async_fn_in_trait)]
pub trait ReqHandleExt<'a>: ReqHandle<'a> {
async fn reply<Req: Request>(
self: Box<Self>,
_: impl Evidence<Req>,
rep: &Req::Response,
) -> io::Result<Receipt<'a>> {
let mut reply = self.start_reply().await?;
rep.encode(reply.writer()).await?;
reply.finish().await
}
}
pub trait RepWriter<'a> {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Receipt<'a>>>;
}
pub trait MsgReader<'a> {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, ()>;
}
impl<'a, T: ?Sized + MsgReader<'a>> MsgReaderExt<'a> for T {}
#[allow(async_fn_in_trait)]
pub trait MsgReaderExt<'a>: MsgReader<'a> {
async fn read<N: Decode>(mut self: Box<Self>) -> io::Result<N> {
let n = N::decode(self.reader()).await;
self.finish().await;
n
}
}
impl<CH: Channel, T: Client + ?Sized> ClientExt<CH> for T {}
/// A form of [Evidence] that doesn't require the value to be kept around
pub struct Witness<T>(PhantomData<T>);
@@ -105,64 +167,52 @@ type IoLock<T> = Rc<Mutex<Pin<Box<T>>>>;
type IoGuard<T> = Bound<MutexGuard<'static, Pin<Box<T>>>, IoLock<T>>;
/// An incoming request. This holds a lock on the ingress channel.
pub struct ReqReader<'a> {
id: u64,
pub struct IoReqReader<'a> {
prefix: &'a [u8],
read: IoGuard<dyn AsyncRead>,
write: &'a Mutex<IoRef<dyn AsyncWrite>>,
}
impl<'a> ReqReader<'a> {
/// Access
pub fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
pub async fn read_req<R: Decode>(&mut self) -> R { R::decode(self.reader()).await }
pub async fn start_reply(self) -> RepWriter<'a> { self.branch().await.start_reply().await }
pub async fn reply<R: Request>(self, req: impl Evidence<R>, rep: &R::Response) -> Receipt<'a> {
self.branch().await.reply(req, rep).await
impl<'a> ReqReader<'a> for IoReqReader<'a> {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>> {
Box::pin(async {
Box::new(IoReqHandle { prefix: self.prefix, write: self.write }) as Box<dyn ReqHandle<'a>>
})
}
pub async fn branch(self) -> ReqHandle<'a> { ReqHandle { id: self.id, write: self.write } }
}
pub struct ReqHandle<'a> {
id: u64,
pub struct IoReqHandle<'a> {
prefix: &'a [u8],
write: &'a Mutex<IoRef<dyn AsyncWrite>>,
}
impl<'a> ReqHandle<'a> {
pub async fn reply<Req: Request>(
self,
_: impl Evidence<Req>,
rep: &Req::Response,
) -> Receipt<'a> {
let mut reply = self.start_reply().await;
rep.encode(reply.writer()).await;
reply.send().await
}
pub async fn start_reply(self) -> RepWriter<'a> {
let mut write = self.write.lock().await;
(!self.id).encode(write.as_mut()).await;
RepWriter { write }
impl<'a> ReqHandle<'a> for IoReqHandle<'a> {
fn start_reply(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>> {
Box::pin(async move {
let mut write = self.write.lock().await;
write.as_mut().write_all(self.prefix).await?;
Ok(Box::new(IoRepWriter { write }) as Box<dyn RepWriter<'a>>)
})
}
}
pub struct RepWriter<'a> {
pub struct IoRepWriter<'a> {
write: MutexGuard<'a, IoRef<dyn AsyncWrite>>,
}
impl<'a> RepWriter<'a> {
pub fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.write.as_mut() }
pub async fn send(mut self) -> Receipt<'a> {
self.writer().flush().await.unwrap();
Receipt(PhantomData)
impl<'a> RepWriter<'a> for IoRepWriter<'a> {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.write.as_mut() }
fn finish(mut self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Receipt<'a>>> {
Box::pin(async move {
self.writer().flush().await?;
Ok(Receipt(PhantomData))
})
}
}
pub struct NotifReader<'a> {
pub struct IoMsgReader<'a> {
_pd: PhantomData<&'a mut ()>,
read: IoGuard<dyn AsyncRead>,
}
impl<'a> NotifReader<'a> {
pub fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
pub async fn read<N: Decode>(mut self) -> N {
let n = N::decode(self.reader()).await;
self.release().await;
n
}
pub async fn release(self) {}
impl<'a> MsgReader<'a> for IoMsgReader<'a> {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> { Box::pin(async {}) }
}
#[derive(Debug)]
@@ -187,14 +237,14 @@ impl IoClient {
}
}
impl Client for IoClient {
fn start_notif(&self) -> LocalBoxFuture<'_, Box<dyn MsgWriter>> {
fn start_notif(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn MsgWriter<'_> + '_>>> {
Box::pin(async {
let mut o = self.lock_out().await;
0u64.encode(o.as_mut()).await;
Box::new(IoNotifWriter { o }) as Box<dyn MsgWriter>
0u64.encode(o.as_mut()).await?;
Ok(Box::new(IoNotifWriter { o }) as Box<dyn MsgWriter>)
})
}
fn start_request(&self) -> LocalBoxFuture<'_, Box<dyn ReqWriter>> {
fn start_request(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn ReqWriter<'_> + '_>>> {
Box::pin(async {
let id = {
let mut id_g = self.id.borrow_mut();
@@ -206,8 +256,8 @@ impl Client for IoClient {
self.subscribe.as_ref().clone().send(ReplySub { id, ack, cb }).await.unwrap();
got_ack.await.unwrap();
let mut w = self.lock_out().await;
id.encode(w.as_mut()).await;
Box::new(IoReqWriter { reply, w }) as Box<dyn ReqWriter>
id.encode(w.as_mut()).await?;
Ok(Box::new(IoReqWriter { reply, w }) as Box<dyn ReqWriter>)
})
}
}
@@ -216,13 +266,15 @@ struct IoReqWriter {
reply: oneshot::Receiver<IoGuard<dyn AsyncRead>>,
w: IoGuard<dyn AsyncWrite>,
}
impl ReqWriter for IoReqWriter {
impl<'a> ReqWriter<'a> for IoReqWriter {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.w.as_mut() }
fn send(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn RepReader>> {
fn send(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepReader<'a> + 'a>>> {
Box::pin(async {
let Self { reply, .. } = *self;
let Self { reply, mut w } = *self;
w.flush().await?;
mem::drop(w);
let i = reply.await.expect("Client dropped before reply received");
Box::new(IoRepReader { i }) as Box<dyn RepReader>
Ok(Box::new(IoRepReader { i }) as Box<dyn RepReader>)
})
}
}
@@ -230,7 +282,7 @@ impl ReqWriter for IoReqWriter {
struct IoRepReader {
i: IoGuard<dyn AsyncRead>,
}
impl RepReader for IoRepReader {
impl<'a> RepReader<'a> for IoRepReader {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.i.as_mut() }
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> { Box::pin(async {}) }
}
@@ -239,11 +291,10 @@ impl RepReader for IoRepReader {
struct IoNotifWriter {
o: IoGuard<dyn AsyncWrite>,
}
impl MsgWriter for IoNotifWriter {
impl<'a> MsgWriter<'a> for IoNotifWriter {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.o.as_mut() }
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> {
self.destructure();
Box::pin(async {})
fn finish(mut self: Box<Self>) -> LocalBoxFuture<'static, io::Result<()>> {
Box::pin(async move { self.o.flush().await })
}
}
@@ -262,12 +313,12 @@ impl CommCtx {
/// parameters are associated with the client and serve to ensure with a runtime
/// check that the correct message families are sent in the correct directions
/// across the channel.
pub fn io_comm<CH: Channel>(
pub fn io_comm(
o: Rc<Mutex<Pin<Box<dyn AsyncWrite>>>>,
i: Mutex<Pin<Box<dyn AsyncRead>>>,
notif: impl for<'a> AsyncFn(NotifReader<'a>),
req: impl for<'a> AsyncFn(ReqReader<'a>) -> Receipt<'a>,
) -> (impl ClientExt<CH>, CommCtx, impl Future<Output = ()>) {
notif: impl for<'a> AsyncFn(Box<dyn MsgReader<'a> + 'a>) -> io::Result<()>,
req: impl for<'a> AsyncFn(Box<dyn ReqReader<'a> + 'a>) -> io::Result<Receipt<'a>>,
) -> (impl Client + 'static, CommCtx, impl Future<Output = io::Result<()>>) {
let i = Rc::new(i);
let (onsub, client) = IoClient::new(o.clone());
let (exit, onexit) = channel(1);
@@ -278,65 +329,76 @@ pub fn io_comm<CH: Channel>(
Exit,
}
let exiting = RefCell::new(false);
let input_stream = stream(async |mut h| {
let input_stream = try_stream(async |mut h| {
loop {
let mut g = Bound::async_new(i.clone(), async |i| i.lock().await).await;
let id = u64::decode(g.as_mut()).await;
h.emit(Event::Input(id, g)).await;
match u64::decode(g.as_mut()).await {
Ok(id) => h.emit(Event::Input(id, g)).await,
Err(e)
if matches!(
e.kind(),
io::ErrorKind::BrokenPipe
| io::ErrorKind::ConnectionAborted
| io::ErrorKind::UnexpectedEof
) =>
h.emit(Event::Exit).await,
Err(e) => return Err(e),
}
}
});
let pending_reqs = RefCell::new(VecDeque::<LocalBoxFuture<()>>::new());
// this stream will never yield a value
let mut fork_stream = pin!(
stream::poll_fn(|cx| {
let mut reqs_g = pending_reqs.borrow_mut();
reqs_g.retain_mut(|req| match req.as_mut().poll(cx) {
Poll::Pending => true,
Poll::Ready(()) => false,
});
if *exiting.borrow() { Poll::Ready(None) } else { Poll::Pending }
})
.fuse()
);
let (mut add_pending_req, fork_future) = LocalSet::new();
let mut fork_stream = pin!(fork_future.fuse().into_stream());
let mut pending_replies = HashMap::new();
{
'body: {
let mut shared = pin!(stream_select!(
pin!(input_stream) as Pin<&mut dyn Stream<Item = Event>>,
onsub.map(Event::Sub),
fork_stream.as_mut(),
onexit.map(|()| Event::Exit),
pin!(input_stream) as Pin<&mut dyn Stream<Item = io::Result<Event>>>,
onsub.map(|sub| Ok(Event::Sub(sub))),
fork_stream.as_mut().map(|res| {
res.map(|()| panic!("this substream cannot exit while the loop is running"))
}),
onexit.map(|()| Ok(Event::Exit)),
));
while let Some(next) = shared.next().await {
match next {
Event::Exit => {
Err(e) => break 'body Err(e),
Ok(Event::Exit) => {
*exiting.borrow_mut() = true;
break;
},
Event::Sub(ReplySub { id, ack, cb }) => {
Ok(Event::Sub(ReplySub { id, ack, cb })) => {
pending_replies.insert(id, cb);
ack.send(()).unwrap();
},
Event::Input(0, read) => {
Ok(Event::Input(0, read)) => {
let notif = &notif;
pending_reqs.borrow_mut().push_back(Box::pin(async move {
notif(NotifReader { _pd: PhantomData, read }).await
}));
let notif_job =
async move { notif(Box::new(IoMsgReader { _pd: PhantomData, read })).await };
add_pending_req.send(Box::pin(notif_job)).await.unwrap();
},
// MSB == 0 is a request, !id where MSB == 1 is the corresponding response
Ok(Event::Input(id, read)) if (id & (1 << (u64::BITS - 1))) == 0 => {
let (o, req) = (o.clone(), &req);
let req_job = async move {
let mut prefix = Vec::new();
(!id).encode_vec(&mut prefix);
let _ = req(Box::new(IoReqReader { prefix: &pin!(prefix), read, write: &o })).await;
Ok(())
};
add_pending_req.send(Box::pin(req_job)).await.unwrap();
},
Ok(Event::Input(id, read)) => {
let cb = pending_replies.remove(&!id).expect("Reply to unrecognized request");
cb.send(read).unwrap_or_else(|_| panic!("Failed to send reply"));
},
// id.msb == 0 is a request, !id where id.msb == 1 is the equivalent response
Event::Input(id, read) =>
if (id & (1 << (u64::BITS - 1))) == 0 {
let (o, req) = (o.clone(), &req);
pending_reqs.borrow_mut().push_back(Box::pin(async move {
let _ = req(ReqReader { id, read, write: &o }).await;
}) as LocalBoxFuture<()>);
} else {
let cb = pending_replies.remove(&!id).expect("Reply to unrecognized request");
cb.send(read).unwrap_or_else(|_| panic!("Failed to send reply"));
},
}
}
Ok(())
}?;
mem::drop(add_pending_req);
while let Some(next) = fork_stream.next().await {
next?
}
fork_stream.as_mut().count().await;
Ok(())
})
}
@@ -347,18 +409,48 @@ mod test {
use futures::channel::mpsc;
use futures::lock::Mutex;
use futures::{SinkExt, StreamExt, join};
use never::Never;
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::{Channel, Request};
use orchid_api_traits::Request;
use test_executors::spin_on;
use unsync_pipe::pipe;
use crate::reqnot::{ClientExt, NotifReader, io_comm};
use crate::reqnot::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm};
#[derive(Clone, Debug, PartialEq, Coding, Hierarchy)]
#[extendable]
struct TestNotif(u64);
#[test]
fn notification() {
spin_on(async {
let (in1, out2) = pipe(1024);
let (in2, out1) = pipe(1024);
let (received, mut on_receive) = mpsc::channel(2);
let (_, recv_ctx, run_recv) = io_comm(
Rc::new(Mutex::new(Box::pin(in2))),
Mutex::new(Box::pin(out2)),
async |notif| {
received.clone().send(notif.read::<TestNotif>().await?).await.unwrap();
Ok(())
},
async |_| panic!("Should receive notif, not request"),
);
let (sender, ..) = io_comm(
Rc::new(Mutex::new(Box::pin(in1))),
Mutex::new(Box::pin(out1)),
async |_| panic!("Should not receive notif"),
async |_| panic!("Should not receive request"),
);
join!(async { run_recv.await.unwrap() }, async {
sender.notify(TestNotif(3)).await.unwrap();
assert_eq!(on_receive.next().await, Some(TestNotif(3)));
sender.notify(TestNotif(4)).await.unwrap();
assert_eq!(on_receive.next().await, Some(TestNotif(4)));
recv_ctx.exit().await;
});
})
}
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extendable]
struct DummyRequest(u64);
@@ -366,64 +458,28 @@ mod test {
type Response = u64;
}
struct TestChannel;
impl Channel for TestChannel {
type Notif = TestNotif;
type Req = DummyRequest;
}
#[test]
fn notification() {
spin_on(async {
let (in1, out2) = pipe(1024);
let (in2, out1) = pipe(1024);
let (received, mut on_receive) = mpsc::channel(2);
let (_, recv_ctx, run_recv) = io_comm::<Never>(
Rc::new(Mutex::new(Box::pin(in2))),
Mutex::new(Box::pin(out2)),
async |notif: NotifReader| {
received.clone().send(notif.read::<TestNotif>().await).await.unwrap();
},
async |_| panic!("Should receive notif, not request"),
);
let (sender, ..) = io_comm::<TestChannel>(
Rc::new(Mutex::new(Box::pin(in1))),
Mutex::new(Box::pin(out1)),
async |_| panic!("Should not receive notif"),
async |_| panic!("Should not receive request"),
);
join!(run_recv, async {
sender.notify(TestNotif(3)).await;
assert_eq!(on_receive.next().await, Some(TestNotif(3)));
sender.notify(TestNotif(4)).await;
assert_eq!(on_receive.next().await, Some(TestNotif(4)));
recv_ctx.exit().await;
});
})
}
#[test]
fn request() {
spin_on(async {
let (in1, out2) = pipe(1024);
let (in2, out1) = pipe(1024);
let (_, srv_ctx, run_srv) = io_comm::<Never>(
let (_, srv_ctx, run_srv) = io_comm(
Rc::new(Mutex::new(Box::pin(in2))),
Mutex::new(Box::pin(out2)),
async |_| panic!("No notifs expected"),
async |mut req| {
let val = req.read_req::<DummyRequest>().await;
let val = req.read_req::<DummyRequest>().await?;
req.reply(&val, &(val.0 + 1)).await
},
);
let (client, client_ctx, run_client) = io_comm::<TestChannel>(
let (client, client_ctx, run_client) = io_comm(
Rc::new(Mutex::new(Box::pin(in1))),
Mutex::new(Box::pin(out1)),
async |_| panic!("Not expecting ingress notif"),
async |_| panic!("Not expecting ingress req"),
);
join!(run_srv, run_client, async {
let response = client.request(DummyRequest(5)).await;
join!(async { run_srv.await.unwrap() }, async { run_client.await.unwrap() }, async {
let response = client.request(DummyRequest(5)).await.unwrap();
assert_eq!(response, 6);
srv_ctx.exit().await;
client_ctx.exit().await;

View File

@@ -1,16 +1,21 @@
//! A pattern for running async code from sync destructors and other
//! unfortunately sync callbacks
//!
//! We create a task_local
//! We create a task_local vecdeque which is moved into a thread_local whenever
//! the task is being polled. A call to [stash] pushes the future onto this
//! deque. Before [with_stash] returns, it pops everything from the deque
//! individually and awaits each of them, pushing any additionally stashed
//! futures onto the back of the same deque.
use std::cell::RefCell;
use std::collections::VecDeque;
use std::pin::Pin;
use some_executor::task_local;
use task_local::task_local;
#[derive(Default)]
struct StashedFutures {
queue: VecDeque<Pin<Box<dyn Future<Output = ()>>>>,
queue: RefCell<VecDeque<Pin<Box<dyn Future<Output = ()>>>>>,
}
task_local! {
@@ -23,7 +28,7 @@ pub async fn with_stash<F: Future>(fut: F) -> F::Output {
STASHED_FUTURES
.scope(StashedFutures::default(), async {
let val = fut.await;
while let Some(fut) = STASHED_FUTURES.with_mut(|sf| sf.unwrap().queue.pop_front()) {
while let Some(fut) = STASHED_FUTURES.with(|sf| sf.queue.borrow_mut().pop_front()) {
fut.await;
}
val
@@ -33,10 +38,7 @@ pub async fn with_stash<F: Future>(fut: F) -> F::Output {
/// Schedule a future to be run before the next [with_stash] guard ends. This is
/// most useful for sending messages from destructors.
pub fn stash<F: Future + 'static>(fut: F) {
STASHED_FUTURES.with_mut(|sf| {
sf.expect("No stash! Timely completion cannot be guaranteed").queue.push_back(Box::pin(async {
fut.await;
}))
})
pub fn stash<F: Future<Output = ()> + 'static>(fut: F) {
(STASHED_FUTURES.try_with(|sf| sf.queue.borrow_mut().push_back(Box::pin(fut))))
.expect("No stash! Timely completion cannot be guaranteed")
}

View File

@@ -8,6 +8,7 @@ edition = "2024"
[dependencies]
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
async-once-cell = "0.5.4"
bound = "0.6.0"
derive_destructure = "1.0.0"
dyn-clone = "1.0.20"
futures = { version = "0.3.31", features = [
@@ -29,7 +30,6 @@ orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
orchid-base = { version = "0.1.0", path = "../orchid-base" }
ordered-float = "5.0.0"
pastey = "0.1.1"
some_executor = "0.6.1"
substack = "1.1.1"
task-local = "0.1.0"
tokio = { version = "1.47.1", optional = true, features = [] }

View File

@@ -14,20 +14,20 @@ use orchid_api_derive::Coding;
use orchid_api_traits::{Coding, Decode, Encode, Request, enc_vec};
use orchid_base::error::{OrcErrv, OrcRes, mk_errv, mk_errv_floating};
use orchid_base::format::{FmtCtx, FmtUnit, Format, fmt};
use orchid_base::interner::is;
use orchid_base::location::Pos;
use orchid_base::name::Sym;
use orchid_base::reqnot::Requester;
use trait_set::trait_set;
use crate::api;
use crate::context::{ctx, i};
use crate::conv::ToExpr;
use crate::entrypoint::request;
// use crate::error::{ProjectError, ProjectResult};
use crate::expr::{Expr, ExprData, ExprHandle, ExprKind};
use crate::gen_expr::GExpr;
use crate::system::{DynSystemCard, atom_info_for, downcast_atom};
use crate::system::{DynSystemCard, atom_by_idx, atom_info_for, cted, downcast_atom};
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)]
pub struct AtomTypeId(pub NonZeroU32);
pub trait AtomCard: 'static + Sized {
@@ -99,13 +99,13 @@ impl ForeignAtom {
ForeignAtom { atom, expr: handle, pos }
}
pub async fn request<M: AtomMethod>(&self, m: M) -> Option<M::Response> {
let rep = (ctx().reqnot().request(api::Fwd(
let rep = (request(api::Fwd(
self.atom.clone(),
Sym::parse(M::NAME, &i()).await.unwrap().tok().to_api(),
enc_vec(&m).await,
Sym::parse(M::NAME).await.unwrap().tok().to_api(),
enc_vec(&m),
)))
.await?;
Some(M::Response::decode(Pin::new(&mut &rep[..])).await)
Some(M::Response::decode_slice(&mut &rep[..]))
}
pub async fn downcast<T: AtomicFeatures>(self) -> Result<TAtom<T>, NotTypAtom> {
TAtom::downcast(self.ex().handle()).await
@@ -119,7 +119,7 @@ impl fmt::Debug for ForeignAtom {
}
impl Format for ForeignAtom {
async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
FmtUnit::from_api(&ctx().reqnot().request(api::ExtAtomPrint(self.atom.clone())).await)
FmtUnit::from_api(&request(api::ExtAtomPrint(self.atom.clone())).await)
}
}
impl ToExpr for ForeignAtom {
@@ -138,8 +138,8 @@ pub struct NotTypAtom {
impl NotTypAtom {
pub async fn mk_err(&self) -> OrcErrv {
mk_errv(
i().i("Not the expected type").await,
format!("The expression {} is not a {}", fmt(&self.expr, &i()).await, self.typ.name()),
is("Not the expected type").await,
format!("The expression {} is not a {}", fmt(&self.expr).await, self.typ.name()),
[self.pos.clone()],
)
}
@@ -172,8 +172,10 @@ impl<A: AtomCard> MethodSetBuilder<A> {
self.handlers.push((
M::NAME,
Rc::new(move |a: &A, req: Pin<&mut dyn AsyncRead>, rep: Pin<&mut dyn AsyncWrite>| {
async { Supports::<M>::handle(a, M::decode(req).await).await.encode(rep).await }
.boxed_local()
async {
Supports::<M>::handle(a, M::decode(req).await.unwrap()).await.encode(rep).await.unwrap()
}
.boxed_local()
}),
));
self
@@ -182,7 +184,7 @@ impl<A: AtomCard> MethodSetBuilder<A> {
pub async fn pack(&self) -> MethodSet<A> {
MethodSet {
handlers: stream::iter(self.handlers.iter())
.then(async |(k, v)| (Sym::parse(k, &i()).await.unwrap(), v.clone()))
.then(async |(k, v)| (Sym::parse(k).await.unwrap(), v.clone()))
.collect()
.await,
}
@@ -234,16 +236,15 @@ impl<A: AtomicFeatures> TAtom<A> {
}
pub async fn request<M: AtomMethod>(&self, req: M) -> M::Response
where A: Supports<M> {
M::Response::decode(Pin::new(
&mut &(ctx().reqnot().request(api::Fwd(
M::Response::decode_slice(
&mut &(request(api::Fwd(
self.untyped.atom.clone(),
Sym::parse(M::NAME, &i()).await.unwrap().tok().to_api(),
enc_vec(&req).await,
Sym::parse(M::NAME).await.unwrap().tok().to_api(),
enc_vec(&req),
)))
.await
.unwrap()[..],
))
.await
)
}
}
impl<A: AtomicFeatures> Deref for TAtom<A> {
@@ -311,9 +312,18 @@ impl Format for AtomFactory {
}
pub async fn err_not_callable() -> OrcErrv {
mk_errv_floating(i().i("This atom is not callable").await, "Attempted to apply value as function")
mk_errv_floating(is("This atom is not callable").await, "Attempted to apply value as function")
}
pub async fn err_not_command() -> OrcErrv {
mk_errv_floating(i().i("This atom is not a command").await, "Settled on an inactionable value")
mk_errv_floating(is("This atom is not a command").await, "Settled on an inactionable value")
}
/// Read the type ID prefix from an atom, return type information and the rest
/// of the data
pub(crate) fn resolve_atom_type(atom: &api::Atom) -> (Box<dyn AtomDynfo>, AtomTypeId, &[u8]) {
let mut data = &atom.data.0[..];
let tid = AtomTypeId::decode_slice(&mut data);
let atom_record = atom_by_idx(cted().inst().card(), tid).expect("Unrecognized atom type ID");
(atom_record, tid, data)
}

View File

@@ -1,11 +1,12 @@
use std::any::{Any, TypeId, type_name};
use std::borrow::Cow;
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::num::NonZero;
use std::ops::Deref;
use std::pin::Pin;
use std::sync::atomic::AtomicU64;
use std::rc::Rc;
use async_once_cell::OnceCell;
use dyn_clone::{DynClone, clone_box};
@@ -19,32 +20,33 @@ use orchid_api_traits::{Decode, Encode, enc_vec};
use orchid_base::error::OrcRes;
use orchid_base::format::{FmtCtx, FmtCtxImpl, FmtUnit, take_first};
use orchid_base::name::Sym;
use task_local::task_local;
use crate::api;
use crate::atom::{
AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet,
MethodSetBuilder, TAtom, err_not_callable, err_not_command, get_info,
};
use crate::context::{SysCtxEntry, ctx, i};
use crate::expr::Expr;
use crate::gen_expr::{GExpr, bot};
use crate::system_ctor::CtedObj;
use crate::system::{cted, sys_id};
pub struct OwnedVariant;
impl AtomicVariant for OwnedVariant {}
impl<A: OwnedAtom + Atomic<Variant = OwnedVariant>> AtomicFeaturesImpl<OwnedVariant> for A {
fn _factory(self) -> AtomFactory {
AtomFactory::new(async move || {
let serial = ctx()
.get_or_default::<ObjStore>()
.next_id
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let atom_id = api::AtomId(NonZero::new(serial + 1).unwrap());
let (typ_id, _) = get_info::<A>(ctx().get::<CtedObj>().inst().card());
let mut data = enc_vec(&typ_id).await;
let obj_store = get_obj_store();
let atom_id = {
let mut id = obj_store.next_id.borrow_mut();
*id += 1;
api::AtomId(NonZero::new(*id + 1).unwrap())
};
let (typ_id, _) = get_info::<A>(cted().inst().card());
let mut data = enc_vec(&typ_id);
self.encode(Pin::<&mut Vec<u8>>::new(&mut data)).await;
ctx().get_or_default::<ObjStore>().objects.read().await.insert(atom_id, Box::new(self));
api::Atom { drop: Some(atom_id), data: api::AtomData(data), owner: ctx().sys_id() }
obj_store.objects.read().await.insert(atom_id, Box::new(self));
api::Atom { drop: Some(atom_id), data: api::AtomData(data), owner: sys_id() }
})
}
fn _info() -> Self::_Info { OwnedAtomDynfo { msbuild: A::reg_reqs(), ms: OnceCell::new() } }
@@ -59,7 +61,7 @@ pub(crate) struct AtomReadGuard<'a> {
}
impl<'a> AtomReadGuard<'a> {
async fn new(id: api::AtomId) -> Self {
let guard = ctx().get_or_default::<ObjStore>().objects.read().await;
let guard = get_obj_store().objects.read().await;
if guard.get(&id).is_none() {
panic!("Received invalid atom ID: {id:?}");
}
@@ -73,7 +75,7 @@ impl Deref for AtomReadGuard<'_> {
/// Remove an atom from the store
pub(crate) async fn take_atom(id: api::AtomId) -> Box<dyn DynOwnedAtom> {
let mut g = ctx().get_or_default::<ObjStore>().objects.write().await;
let mut g = get_obj_store().objects.write().await;
g.remove(&id).unwrap_or_else(|| panic!("Received invalid atom ID: {}", id.0))
}
@@ -86,7 +88,7 @@ impl<T: OwnedAtom> AtomDynfo for OwnedAtomDynfo<T> {
fn name(&self) -> &'static str { type_name::<T>() }
fn decode<'a>(&'a self, AtomCtx(data, ..): AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>> {
Box::pin(async {
Box::new(<T as AtomCard>::Data::decode(Pin::new(&mut &data[..])).await) as Box<dyn Any>
Box::new(<T as AtomCard>::Data::decode_slice(&mut &data[..])) as Box<dyn Any>
})
}
fn call(&self, AtomCtx(_, id): AtomCtx, arg: Expr) -> LocalBoxFuture<'_, GExpr> {
@@ -127,7 +129,7 @@ impl<T: OwnedAtom> AtomDynfo for OwnedAtomDynfo<T> {
) -> LocalBoxFuture<'a, Option<Vec<Expr>>> {
Box::pin(async move {
let id = id.unwrap();
id.encode(write.as_mut()).await;
id.encode(write.as_mut()).await.unwrap();
AtomReadGuard::new(id).await.dyn_serialize(write).await
})
}
@@ -155,7 +157,7 @@ pub trait DeserializeCtx: Sized {
struct DeserCtxImpl<'a>(&'a [u8]);
impl DeserializeCtx for DeserCtxImpl<'_> {
async fn read<T: Decode>(&mut self) -> T { T::decode(Pin::new(&mut self.0)).await }
async fn read<T: Decode>(&mut self) -> T { T::decode(Pin::new(&mut self.0)).await.unwrap() }
fn is_empty(&self) -> bool { self.0.is_empty() }
}
@@ -266,7 +268,7 @@ impl<T: OwnedAtom> DynOwnedAtom for T {
fn atom_tid(&self) -> TypeId { TypeId::of::<T>() }
fn as_any_ref(&self) -> &dyn Any { self }
fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()> {
async { self.val().await.as_ref().encode(buffer).await }.boxed_local()
async { self.val().await.as_ref().encode(buffer).await.unwrap() }.boxed_local()
}
fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr> {
self.call_ref(arg).boxed_local()
@@ -279,7 +281,7 @@ impl<T: OwnedAtom> DynOwnedAtom for T {
}
fn dyn_free(self: Box<Self>) -> LocalBoxFuture<'static, ()> { self.free().boxed_local() }
fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit> {
async move { self.print_atom(&FmtCtxImpl { i: &i() }).await }.boxed_local()
async move { self.print_atom(&FmtCtxImpl::default()).await }.boxed_local()
}
fn dyn_serialize<'a>(
&'a self,
@@ -294,13 +296,24 @@ impl<T: OwnedAtom> DynOwnedAtom for T {
#[derive(Default)]
pub(crate) struct ObjStore {
pub(crate) next_id: AtomicU64,
pub(crate) next_id: RefCell<u64>,
pub(crate) objects: RwLock<MemoMap<api::AtomId, Box<dyn DynOwnedAtom>>>,
}
impl SysCtxEntry for ObjStore {}
task_local! {
static OBJ_STORE: Rc<ObjStore>;
}
pub(crate) fn with_obj_store<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
Box::pin(OBJ_STORE.scope(Rc::new(ObjStore::default()), fut))
}
pub(crate) fn get_obj_store() -> Rc<ObjStore> {
OBJ_STORE.try_with(|store| store.clone()).expect("Owned atom store not initialized")
}
pub async fn own<A: OwnedAtom>(typ: &TAtom<A>) -> A {
let g = ctx().get_or_default::<ObjStore>().objects.read().await;
let g = get_obj_store().objects.read().await;
let atom_id = typ.untyped.atom.drop.expect("Owned atoms always have a drop ID");
let dyn_atom =
g.get(&atom_id).expect("Atom ID invalid; atom type probably not owned by this crate");
@@ -308,8 +321,7 @@ pub async fn own<A: OwnedAtom>(typ: &TAtom<A>) -> A {
}
pub async fn debug_print_obj_store(show_atoms: bool) {
let ctx = ctx();
let store = ctx.get_or_default::<ObjStore>();
let store = get_obj_store();
let keys = store.objects.read().await.keys().cloned().collect_vec();
let mut message = "Atoms in store:".to_string();
if !show_atoms {

View File

@@ -8,6 +8,7 @@ use futures::{AsyncRead, AsyncWrite, FutureExt};
use orchid_api_traits::{Coding, enc_vec};
use orchid_base::error::OrcRes;
use orchid_base::format::FmtUnit;
use orchid_base::logging::logger;
use orchid_base::name::Sym;
use crate::api;
@@ -15,20 +16,19 @@ use crate::atom::{
AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet,
MethodSetBuilder, err_not_callable, err_not_command, get_info,
};
use crate::context::ctx;
use crate::expr::Expr;
use crate::gen_expr::{GExpr, bot};
use crate::system_ctor::CtedObj;
use crate::system::{cted, sys_id};
pub struct ThinVariant;
impl AtomicVariant for ThinVariant {}
impl<A: ThinAtom + Atomic<Variant = ThinVariant>> AtomicFeaturesImpl<ThinVariant> for A {
fn _factory(self) -> AtomFactory {
AtomFactory::new(async move || {
let (id, _) = get_info::<A>(ctx().get::<CtedObj>().inst().card());
let mut buf = enc_vec(&id).await;
self.encode(Pin::new(&mut buf)).await;
api::Atom { drop: None, data: api::AtomData(buf), owner: ctx().sys_id() }
let (id, _) = get_info::<A>(cted().inst().card());
let mut buf = enc_vec(&id);
self.encode_vec(&mut buf);
api::Atom { drop: None, data: api::AtomData(buf), owner: sys_id() }
})
}
fn _info() -> Self::_Info { ThinAtomDynfo { msbuild: Self::reg_reqs(), ms: OnceCell::new() } }
@@ -41,18 +41,18 @@ pub struct ThinAtomDynfo<T: ThinAtom> {
}
impl<T: ThinAtom> AtomDynfo for ThinAtomDynfo<T> {
fn print<'a>(&self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit> {
Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.print().await })
Box::pin(async move { T::decode_slice(&mut &buf[..]).print().await })
}
fn tid(&self) -> TypeId { TypeId::of::<T>() }
fn name(&self) -> &'static str { type_name::<T>() }
fn decode<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>> {
Box::pin(async { Box::new(T::decode(Pin::new(&mut &buf[..])).await) as Box<dyn Any> })
Box::pin(async { Box::new(T::decode_slice(&mut &buf[..])) as Box<dyn Any> })
}
fn call<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> {
Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.call(arg).await })
Box::pin(async move { T::decode_slice(&mut &buf[..]).call(arg).await })
}
fn call_ref<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> {
Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.call(arg).await })
Box::pin(async move { T::decode_slice(&mut &buf[..]).call(arg).await })
}
fn handle_req<'a, 'm1: 'a, 'm2: 'a>(
&'a self,
@@ -63,14 +63,14 @@ impl<T: ThinAtom> AtomDynfo for ThinAtomDynfo<T> {
) -> LocalBoxFuture<'a, bool> {
Box::pin(async move {
let ms = self.ms.get_or_init(self.msbuild.pack()).await;
ms.dispatch(&T::decode(Pin::new(&mut &buf[..])).await, key, req, rep).await
ms.dispatch(&T::decode_slice(&mut &buf[..]), key, req, rep).await
})
}
fn command<'a>(
&'a self,
AtomCtx(buf, _): AtomCtx<'a>,
) -> LocalBoxFuture<'a, OrcRes<Option<GExpr>>> {
async move { T::decode(Pin::new(&mut &buf[..])).await.command().await }.boxed_local()
async move { T::decode_slice(&mut &buf[..]).command().await }.boxed_local()
}
fn serialize<'a, 'b: 'a>(
&'a self,
@@ -78,18 +78,18 @@ impl<T: ThinAtom> AtomDynfo for ThinAtomDynfo<T> {
write: Pin<&'b mut dyn AsyncWrite>,
) -> LocalBoxFuture<'a, Option<Vec<Expr>>> {
Box::pin(async {
T::decode(Pin::new(&mut &ctx.0[..])).await.encode(write).await;
T::decode_slice(&mut &ctx.0[..]).encode(write).await.unwrap();
Some(Vec::new())
})
}
fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom> {
assert!(refs.is_empty(), "Refs found when deserializing thin atom");
Box::pin(async { T::decode(Pin::new(&mut &data[..])).await._factory().build().await })
Box::pin(async { T::decode_slice(&mut &data[..])._factory().build().await })
}
fn drop<'a>(&'a self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, ()> {
Box::pin(async move {
let string_self = T::decode(Pin::new(&mut &buf[..])).await.print().await;
writeln!(ctx().logger(), "Received drop signal for non-drop atom {string_self:?}");
let string_self = T::decode_slice(&mut &buf[..]).print().await;
writeln!(logger(), "Received drop signal for non-drop atom {string_self:?}");
})
}
}

View File

@@ -1,90 +0,0 @@
use std::any::{Any, TypeId, type_name};
use std::fmt;
use std::num::NonZero;
use std::rc::Rc;
use memo_map::MemoMap;
use orchid_base::builtin::Spawner;
use orchid_base::interner::Interner;
use orchid_base::logging::Logger;
use orchid_base::reqnot::ReqNot;
use task_local::task_local;
use crate::api;
use crate::system_ctor::CtedObj;
#[derive(Clone)]
pub struct SysCtx(Rc<MemoMap<TypeId, Box<dyn Any>>>);
impl SysCtx {
pub fn new(
id: api::SysId,
i: Interner,
reqnot: ReqNot<api::ExtMsgSet>,
spawner: Spawner,
logger: Logger,
cted: CtedObj,
) -> Self {
let this = Self(Rc::new(MemoMap::new()));
this.add(id).add(i).add(reqnot).add(spawner).add(logger).add(cted);
this
}
pub fn add<T: SysCtxEntry>(&self, t: T) -> &Self {
assert!(self.0.insert(TypeId::of::<T>(), Box::new(t)), "Key already exists");
self
}
pub fn get_or_insert<T: SysCtxEntry>(&self, f: impl FnOnce() -> T) -> &T {
(self.0.get_or_insert_owned(TypeId::of::<T>(), || Box::new(f())).downcast_ref())
.expect("Keyed by TypeId")
}
pub fn get_or_default<T: SysCtxEntry + Default>(&self) -> &T { self.get_or_insert(T::default) }
pub fn try_get<T: SysCtxEntry>(&self) -> Option<&T> {
Some(self.0.get(&TypeId::of::<T>())?.downcast_ref().expect("Keyed by TypeId"))
}
pub fn get<T: SysCtxEntry>(&self) -> &T {
self.try_get().unwrap_or_else(|| panic!("Context {} missing", type_name::<T>()))
}
/// Shorthand to get the messaging link
pub fn reqnot(&self) -> &ReqNot<api::ExtMsgSet> { self.get::<ReqNot<api::ExtMsgSet>>() }
/// Shorthand to get the system ID
pub fn sys_id(&self) -> api::SysId { *self.get::<api::SysId>() }
/// Spawn a task that will eventually be executed asynchronously
pub fn spawn(&self, f: impl Future<Output = ()> + 'static) {
(self.get::<Spawner>())(Box::pin(CTX.scope(self.clone(), f)))
}
/// Shorthand to get the logger
pub fn logger(&self) -> &Logger { self.get::<Logger>() }
/// Shorthand to get the constructed system object
pub fn cted(&self) -> &CtedObj { self.get::<CtedObj>() }
}
impl fmt::Debug for SysCtx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SysCtx({:?})", self.sys_id())
}
}
pub trait SysCtxEntry: 'static + Sized {}
impl SysCtxEntry for api::SysId {}
impl SysCtxEntry for ReqNot<api::ExtMsgSet> {}
impl SysCtxEntry for Spawner {}
impl SysCtxEntry for CtedObj {}
impl SysCtxEntry for Logger {}
impl SysCtxEntry for Interner {}
task_local! {
static CTX: SysCtx;
}
pub async fn with_ctx<F: Future>(ctx: SysCtx, f: F) -> F::Output { CTX.scope(ctx, f).await }
pub fn ctx() -> SysCtx { CTX.get() }
/// Shorthand to get the [Interner] instance
pub fn i() -> Interner { ctx().get::<Interner>().clone() }
pub fn mock_ctx() -> SysCtx {
let ctx = SysCtx(Rc::default());
ctx
.add(Logger::new(api::LogStrategy::StdErr))
.add(Interner::new_master())
.add::<Spawner>(Rc::new(|_| panic!("Cannot fork in test environment")))
.add(api::SysId(NonZero::<u16>::MIN));
ctx
}

View File

@@ -4,11 +4,11 @@ use std::pin::Pin;
use dyn_clone::DynClone;
use never::Never;
use orchid_base::error::{OrcErrv, OrcRes, mk_errv};
use orchid_base::interner::is;
use orchid_base::location::Pos;
use trait_set::trait_set;
use crate::atom::{AtomicFeatures, ForeignAtom, TAtom, ToAtom};
use crate::context::i;
use crate::expr::Expr;
use crate::gen_expr::{GExpr, atom, bot};
@@ -27,7 +27,7 @@ impl<T: TryFromExpr, U: TryFromExpr> TryFromExpr for (T, U) {
}
async fn err_not_atom(pos: Pos) -> OrcErrv {
mk_errv(i().i("Expected an atom").await, "This expression is not an atom", [pos])
mk_errv(is("Expected an atom").await, "This expression is not an atom", [pos])
}
impl TryFromExpr for ForeignAtom {

View File

@@ -1,453 +1,438 @@
use std::cell::RefCell;
use std::future::Future;
use std::mem;
use std::num::NonZero;
use std::pin::Pin;
use std::rc::{Rc, Weak};
use std::rc::Rc;
use std::{io, mem};
use futures::channel::mpsc::{Receiver, Sender, channel};
use futures::future::{LocalBoxFuture, join_all};
use futures::lock::Mutex;
use futures::{FutureExt, SinkExt, StreamExt, stream, stream_select};
use futures_locks::RwLock;
use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, StreamExt, stream};
use hashbrown::HashMap;
use itertools::Itertools;
use orchid_api_traits::{Decode, UnderRoot, enc_vec};
use orchid_base::builtin::{ExtInit, ExtPort, Spawner};
use orchid_api::{ExtHostNotif, ExtHostReq};
use orchid_api_traits::{Decode, Encode, Request, UnderRoot, enc_vec};
use orchid_base::char_filter::{char_filter_match, char_filter_union, mk_char_filter};
use orchid_base::clone;
use orchid_base::error::Reporter;
use orchid_base::interner::{Interner, Tok};
use orchid_base::logging::Logger;
use orchid_base::error::try_with_reporter;
use orchid_base::interner::{es, is, with_interner};
use orchid_base::logging::{Logger, with_logger};
use orchid_base::name::Sym;
use orchid_base::parse::{Comment, Snippet};
use orchid_base::reqnot::{ReqNot, ReqReader, Requester};
use orchid_base::reqnot::{
Client, ClientExt, CommCtx, MsgReader, MsgReaderExt, Receipt, RepWriter, ReqHandle, ReqHandleExt,
ReqReader, ReqReaderExt, Witness, io_comm,
};
use orchid_base::stash::with_stash;
use orchid_base::tree::{TokenVariant, ttv_from_api};
use substack::Substack;
use trait_set::trait_set;
use task_local::task_local;
use crate::api;
use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId};
use crate::atom_owned::take_atom;
use crate::context::{SysCtx, ctx, i, with_ctx};
use crate::atom::{AtomCtx, AtomTypeId, resolve_atom_type};
use crate::atom_owned::{take_atom, with_obj_store};
use crate::expr::{BorrowedExprStore, Expr, ExprHandle};
use crate::ext_port::ExtPort;
use crate::func_atom::with_funs_ctx;
use crate::interner::new_interner;
use crate::lexer::{LexContext, ekey_cascade, ekey_not_applicable};
use crate::parser::{PTokTree, ParsCtx, get_const, linev_into_api};
use crate::system::atom_by_idx;
use crate::system_ctor::{CtedObj, DynSystemCtor};
use crate::tree::{LazyMemberFactory, TreeIntoApiCtxImpl};
use crate::parser::{PTokTree, ParsCtx, get_const, linev_into_api, with_parsed_const_ctx};
use crate::reflection::with_refl_roots;
use crate::system::{SysCtx, atom_by_idx, cted, with_sys};
use crate::system_ctor::{CtedObj, DynSystemCtor, SystemCtor};
use crate::tree::{TreeIntoApiCtxImpl, get_lazy, with_lazy_member_store};
task_local::task_local! {
static CLIENT: Rc<dyn Client>;
static CTX: Rc<RefCell<Option<CommCtx>>>;
}
fn get_client() -> Rc<dyn Client> {
CLIENT.with(|c| c.expect("Client not set, not running inside a duplex reqnot channel!").clone())
fn get_client() -> Rc<dyn Client> { CLIENT.get() }
pub async fn exit() {
let cx = CTX.get().borrow_mut().take();
cx.unwrap().exit().await
}
/// Sent the client used for global [request] and [notify] functions within the
/// runtime of this future
pub async fn with_client<F: Future>(c: Rc<dyn Client>, fut: F) -> F::Output {
CLIENT.scope(c, fut).await
pub async fn with_comm<F: Future>(c: Rc<dyn Client>, ctx: CommCtx, fut: F) -> F::Output {
CLIENT.scope(c, CTX.scope(Rc::new(RefCell::new(Some(ctx))), fut)).await
}
/// Send a request through the global client's [ClientExt::request]
pub async fn request<T: Request + UnderRoot<Root: Encode>>(t: T) -> T::Response {
get_client().request(t).await
pub async fn request<T: Request + UnderRoot<Root = ExtHostReq>>(t: T) -> T::Response {
get_client().request(t).await.unwrap()
}
/// Send a notification through the global client's [ClientExt::notify]
pub async fn notify<T: UnderRoot<Root: Encode> + 'static>(t: T) { get_client().notify(t).await }
pub type ExtReq<'a> = ReqReader<'a, api::ExtMsgSet>;
pub type ExtReqNot = ReqNot<api::ExtMsgSet>;
pub struct ExtensionData {
pub name: &'static str,
pub systems: &'static [&'static dyn DynSystemCtor],
}
impl ExtensionData {
pub fn new(name: &'static str, systems: &'static [&'static dyn DynSystemCtor]) -> Self {
Self { name, systems }
}
}
pub enum MemberRecord {
Gen(Vec<Tok<String>>, LazyMemberFactory),
Res,
pub async fn notify<T: UnderRoot<Root = ExtHostNotif>>(t: T) {
get_client().notify(t).await.unwrap()
}
pub struct SystemRecord {
lazy_members: Mutex<HashMap<api::TreeId, MemberRecord>>,
ctx: SysCtx,
cted: CtedObj,
}
trait_set! {
pub trait WithAtomRecordCallback<'a, T> = AsyncFnOnce(
Box<dyn AtomDynfo>,
AtomTypeId,
&'a [u8]
) -> T
type SystemTable = RefCell<HashMap<api::SysId, Rc<SystemRecord>>>;
task_local! {
static SYSTEM_TABLE: SystemTable;
}
pub async fn with_atom_record<'a, F: Future<Output = SysCtx>, T>(
get_sys_ctx: &impl Fn(api::SysId) -> F,
atom: &'a api::Atom,
cb: impl WithAtomRecordCallback<'a, T>,
) -> T {
let mut data = &atom.data.0[..];
let ctx = get_sys_ctx(atom.owner).await;
let inst = ctx.get::<CtedObj>().inst();
let id = AtomTypeId::decode(Pin::new(&mut data)).await;
let atom_record = atom_by_idx(inst.card(), id.clone()).expect("Atom ID reserved");
with_ctx(ctx, async move { cb(atom_record, id, data).await }).await
async fn with_sys_record<F: Future>(id: api::SysId, fut: F) -> F::Output {
let cted = SYSTEM_TABLE.with(|tbl| {
eprintln!(
"Existing systems are {}",
tbl.borrow().iter().map(|(k, v)| format!("{k:?}={:?}", v.cted)).join(";")
);
let sys = tbl.borrow().get(&id).expect("Invalid sys ID").cted.clone();
eprintln!("Selected {:?}", sys);
sys
});
with_sys(SysCtx(id, cted), fut).await
}
pub struct ExtensionOwner {
_interner_cell: Rc<RefCell<Option<Interner>>>,
_systems_lock: Rc<RwLock<HashMap<api::SysId, SystemRecord>>>,
out_recv: Mutex<Receiver<Vec<u8>>>,
out_send: Sender<Vec<u8>>,
pub trait ContextModifier: 'static {
fn apply<'a>(self: Box<Self>, fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()>;
}
impl ExtPort for ExtensionOwner {
fn send<'a>(&'a self, msg: &'a [u8]) -> LocalBoxFuture<'a, ()> {
Box::pin(async { self.out_send.clone().send(msg.to_vec()).boxed_local().await.unwrap() })
}
fn recv(&self) -> LocalBoxFuture<'_, Option<Vec<u8>>> {
Box::pin(async { self.out_recv.lock().await.next().await })
impl<F: AsyncFnOnce(LocalBoxFuture<'_, ()>) + 'static> ContextModifier for F {
fn apply<'a>(self: Box<Self>, fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
Box::pin((self)(fut))
}
}
pub fn extension_init(
data: ExtensionData,
host_header: api::HostHeader,
spawner: Spawner,
) -> ExtInit {
let api::HostHeader { log_strategy, msg_logs } = host_header;
let decls = (data.systems.iter().enumerate())
.map(|(id, sys)| (u16::try_from(id).expect("more than u16max system ctors"), sys))
.map(|(id, sys)| sys.decl(api::SysDeclId(NonZero::new(id + 1).unwrap())))
.collect_vec();
let systems_lock = Rc::new(RwLock::new(HashMap::<api::SysId, SystemRecord>::new()));
let ext_header = api::ExtensionHeader { name: data.name.to_string(), systems: decls.clone() };
let (out_send, in_recv) = channel::<Vec<u8>>(1);
let (in_send, out_recv) = channel::<Vec<u8>>(1);
let (exit_send, exit_recv) = channel(1);
let logger = Logger::new(log_strategy);
let msg_logger = Logger::new(msg_logs);
let interner_cell = Rc::new(RefCell::new(None::<Interner>));
let interner_weak = Rc::downgrade(&interner_cell);
let systems_weak = Rc::downgrade(&systems_lock);
let get_ctx = clone!(systems_weak; move |id: api::SysId| clone!(systems_weak; async move {
let systems =
systems_weak.upgrade().expect("System table dropped before request processing done");
systems.read().await.get(&id).expect("System not found").ctx.clone()
}));
let init_ctx = {
clone!(interner_weak, spawner, logger);
move |id: api::SysId, cted: CtedObj, reqnot: ReqNot<api::ExtMsgSet>| {
clone!(interner_weak, spawner, logger; async move {
let interner_rc =
interner_weak.upgrade().expect("System construction order while shutting down");
let i = interner_rc.borrow().clone().expect("mk_ctx called very early, no interner!");
SysCtx::new(id, i, reqnot, spawner, logger, cted)
})
}
};
let rn = ReqNot::<api::ExtMsgSet>::new(
msg_logger.clone(),
move |a, _| {
clone!(in_send mut);
Box::pin(async move { in_send.send(a.to_vec()).await.unwrap() })
},
{
clone!(exit_send);
move |n, _| {
clone!(exit_send mut);
async move {
match n {
api::HostExtNotif::Exit => exit_send.send(()).await.unwrap(),
pub struct ExtensionBuilder {
pub name: &'static str,
pub systems: Vec<Box<dyn DynSystemCtor>>,
pub context: Vec<Box<dyn ContextModifier>>,
}
impl ExtensionBuilder {
pub fn new(name: &'static str) -> Self { Self { name, systems: Vec::new(), context: Vec::new() } }
pub fn system(mut self, ctor: impl SystemCtor) -> Self {
self.systems.push(Box::new(ctor) as Box<_>);
self
}
pub fn add_context(&mut self, fun: impl ContextModifier) {
self.context.push(Box::new(fun) as Box<_>);
}
pub fn context(mut self, fun: impl ContextModifier) -> Self {
self.add_context(fun);
self
}
pub fn build(mut self, mut ctx: ExtPort) {
self.add_context(with_funs_ctx);
self.add_context(with_parsed_const_ctx);
self.add_context(with_obj_store);
self.add_context(with_lazy_member_store);
self.add_context(with_refl_roots);
(ctx.spawn)(Box::pin(async move {
let api::HostHeader { log_strategy, msg_logs } =
api::HostHeader::decode(ctx.input.as_mut()).await.unwrap();
let decls = (self.systems.iter().enumerate())
.map(|(id, sys)| (u16::try_from(id).expect("more than u16max system ctors"), sys))
.map(|(id, sys)| sys.decl(api::SysDeclId(NonZero::new(id + 1).unwrap())))
.collect_vec();
api::ExtensionHeader { name: self.name.to_string(), systems: decls.clone() }
.encode(ctx.output.as_mut())
.await
.unwrap();
ctx.output.as_mut().flush().await.unwrap();
let logger = Logger::new(log_strategy);
let logger2 = logger.clone();
let msg_logger = Logger::new(msg_logs);
let (client, ctx, run_extension) = io_comm(
Rc::new(Mutex::new(ctx.output)),
Mutex::new(ctx.input),
async move |n: Box<dyn MsgReader<'_>>| {
match n.read().await.unwrap() {
api::HostExtNotif::Exit => exit().await,
}
}
.boxed_local()
}
},
{
clone!(logger, get_ctx, init_ctx, systems_weak, interner_weak, decls, msg_logger);
move |hand, req| {
clone!(logger, get_ctx, init_ctx, systems_weak, interner_weak, decls, msg_logger);
async move {
let interner_cell = interner_weak.upgrade().expect("Interner dropped before request");
let interner =
interner_cell.borrow().clone().expect("Request arrived before interner set");
if !matches!(req, api::HostExtReq::AtomReq(api::AtomReq::AtomPrint(_))) {
writeln!(msg_logger, "{} extension received request {req:?}", data.name);
}
match req {
api::HostExtReq::SystemDrop(sys_drop) => {
if let Some(rc) = systems_weak.upgrade() {
mem::drop(rc.write().await.remove(&sys_drop.0))
}
hand.handle(&sys_drop, &()).await
},
api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) =>
with_ctx(get_ctx(sys_id).await, async move {
take_atom(atom).await.dyn_free().await;
hand.handle(&atom_drop, &()).await
})
.await,
api::HostExtReq::Ping(ping @ api::Ping) => hand.handle(&ping, &()).await,
api::HostExtReq::Sweep(api::Sweep) => todo!(),
api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => {
let (sys_id, _) = (decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system))
.expect("NewSystem call received for invalid system");
let cted = data.systems[sys_id].new_system(&new_sys);
with_ctx(init_ctx(new_sys.id, cted.clone(), hand.reqnot()).await, async move {
let lex_filter =
cted.inst().dyn_lexers().iter().fold(api::CharFilter(vec![]), |cf, lx| {
char_filter_union(&cf, &mk_char_filter(lx.char_filter().iter().cloned()))
});
let lazy_members = Mutex::new(HashMap::new());
let const_root = stream::iter(cted.inst().dyn_env().await)
.then(|mem| {
let lazy_mems = &lazy_members;
async move {
let name = i().i(&mem.name).await;
Ok(())
},
async move |mut reader| {
with_stash(async {
let req = reader.read_req().await.unwrap();
let handle = reader.finish().await;
if !matches!(req, api::HostExtReq::AtomReq(api::AtomReq::AtomPrint(_))) {
writeln!(msg_logger, "{} extension received request {req:?}", self.name);
}
match req {
api::HostExtReq::SystemDrop(sys_drop) => {
SYSTEM_TABLE.with(|l| l.borrow_mut().remove(&sys_drop.0));
handle.reply(&sys_drop, &()).await
},
api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) =>
with_sys_record(sys_id, async {
take_atom(atom).await.dyn_free().await;
handle.reply(&atom_drop, &()).await
})
.await,
api::HostExtReq::Ping(ping @ api::Ping) => handle.reply(&ping, &()).await,
api::HostExtReq::Sweep(api::Sweep) => todo!(),
api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => {
let (ctor_idx, _) =
(decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system))
.expect("NewSystem call received for invalid system");
let cted = self.systems[ctor_idx].new_system(&new_sys);
let record = Rc::new(SystemRecord { cted: cted.clone() });
SYSTEM_TABLE.with(|tbl| {
let mut g = tbl.borrow_mut();
g.insert(new_sys.id, record);
});
with_sys_record(new_sys.id, async {
let lex_filter =
cted.inst().dyn_lexers().iter().fold(api::CharFilter(vec![]), |cf, lx| {
char_filter_union(&cf, &mk_char_filter(lx.char_filter().iter().cloned()))
});
let const_root = stream::iter(cted.inst().dyn_env().await)
.then(async |mem| {
let name = is(&mem.name).await;
let mut tia_ctx = TreeIntoApiCtxImpl {
lazy_members: &mut *lazy_mems.lock().await,
basepath: &[],
path: Substack::Bottom.push(name.clone()),
};
(name.to_api(), mem.kind.into_api(&mut tia_ctx).await)
}
})
.collect()
})
.collect()
.await;
let prelude =
cted.inst().dyn_prelude().await.iter().map(|sym| sym.to_api()).collect();
let line_types = join_all(
(cted.inst().dyn_parsers().iter())
.map(async |p| is(p.line_head()).await.to_api()),
)
.await;
let prelude =
cted.inst().dyn_prelude().await.iter().map(|sym| sym.to_api()).collect();
let record = SystemRecord { ctx: ctx(), lazy_members };
let systems = systems_weak.upgrade().expect("System constructed during shutdown");
systems.write().await.insert(new_sys.id, record);
let line_types = join_all(
(cted.inst().dyn_parsers().iter())
.map(|p| async { interner.i(p.line_head()).await.to_api() }),
)
.await;
let response =
api::NewSystemResponse { lex_filter, const_root, line_types, prelude };
hand.handle(&new_sys, &response).await
})
.await
},
api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) =>
with_ctx(get_ctx(sys_id).await, async move {
let systems = systems_weak.upgrade().expect("Member queried during shutdown");
let systems_g = systems.read().await;
let mut lazy_members =
systems_g.get(&sys_id).expect("System not found").lazy_members.lock().await;
let (path, cb) = match lazy_members.insert(tree_id, MemberRecord::Res) {
None => panic!("Tree for ID not found"),
Some(MemberRecord::Res) => panic!("This tree has already been transmitted"),
Some(MemberRecord::Gen(path, cb)) => (path, cb),
};
let tree = cb.build(Sym::new(path.clone(), &interner).await.unwrap()).await;
let mut tia_ctx = TreeIntoApiCtxImpl {
path: Substack::Bottom,
basepath: &path,
lazy_members: &mut lazy_members,
};
hand.handle(&get_tree, &tree.into_api(&mut tia_ctx).await).await
})
.await,
api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => {
let fwd_tok = hand.will_handle_as(&fwd);
let api::SysFwded(sys_id, payload) = fwd;
let ctx = get_ctx(sys_id).await;
with_ctx(ctx.clone(), async move {
let sys = ctx.cted().inst();
let reply = Rc::new(RefCell::new(None));
let reply2 = reply.clone();
let sub_hand = ExtReq::new(hand.reqnot(), async move |v| {
reply2.borrow_mut().replace(v);
});
sys.dyn_request(sub_hand, payload).await;
let reply_buf =
reply.borrow_mut().take().expect("Request discarded but did not throw");
hand.handle_as(fwd_tok, &reply_buf).await
})
.await
},
api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, src, text, pos, id }) =>
with_ctx(get_ctx(sys).await, async move {
let text = Tok::from_api(text, &i()).await;
let src = Sym::from_api(src, &i()).await;
let rep = Reporter::new();
let expr_store = BorrowedExprStore::new();
let trigger_char = text.chars().nth(pos as usize).unwrap();
let ekey_na = ekey_not_applicable().await;
let ekey_cascade = ekey_cascade().await;
let lexers = ctx().cted().inst().dyn_lexers();
for lx in lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char))
{
let ctx = LexContext::new(&expr_store, &text, id, pos, src.clone(), &rep);
match lx.lex(&text[pos as usize..], &ctx).await {
Err(e) if e.any(|e| *e == ekey_na) => continue,
Err(e) => {
let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api()));
expr_store.dispose().await;
return hand.handle(&lex, &eopt).await;
let response =
api::NewSystemResponse { lex_filter, const_root, line_types, prelude };
handle.reply(&new_sys, &response).await
})
.await
},
api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) =>
with_sys_record(sys_id, async {
let (path, tree) = get_lazy(tree_id).await;
let mut tia_ctx =
TreeIntoApiCtxImpl { path: Substack::Bottom, basepath: &path[..] };
handle.reply(&get_tree, &tree.into_api(&mut tia_ctx).await).await
})
.await,
api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => {
let fwd_tok = Witness::of(&fwd);
let api::SysFwded(sys_id, payload) = fwd;
with_sys_record(sys_id, async {
struct TrivialReqCycle<'a> {
req: &'a [u8],
rep: &'a mut Vec<u8>,
}
impl<'a> ReqReader<'a> for TrivialReqCycle<'a> {
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> {
Pin::new(&mut self.req) as Pin<&mut _>
}
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>> {
Box::pin(async { self as Box<_> })
}
}
impl<'a> ReqHandle<'a> for TrivialReqCycle<'a> {
fn start_reply(
self: Box<Self>,
) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>> {
Box::pin(async { Ok(self as Box<_>) })
}
}
impl<'a> RepWriter<'a> for TrivialReqCycle<'a> {
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> {
Pin::new(&mut self.rep) as Pin<&mut _>
}
fn finish(
self: Box<Self>,
) -> LocalBoxFuture<'a, io::Result<orchid_base::reqnot::Receipt<'a>>>
{
Box::pin(async { Ok(Receipt::_new()) })
}
}
let mut reply = Vec::new();
let req = TrivialReqCycle { req: &payload, rep: &mut reply };
let _ = cted().inst().dyn_request(Box::new(req)).await;
handle.reply(fwd_tok, &reply).await
})
.await
},
api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, src, text, pos, id }) =>
with_sys_record(sys, async {
let text = es(text).await;
let src = Sym::from_api(src).await;
let expr_store = BorrowedExprStore::new();
let trigger_char = text.chars().nth(pos as usize).unwrap();
let ekey_na = ekey_not_applicable().await;
let ekey_cascade = ekey_cascade().await;
let lexers = cted().inst().dyn_lexers();
writeln!(
logger,
"sys={sys:?}, tc={trigger_char}, lexers={}",
lexers.iter().map(|l| format!("{l:?}")).join(",")
);
for lx in
lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char))
{
let ctx = LexContext::new(&expr_store, &text, id, pos, src.clone());
match try_with_reporter(lx.lex(&text[pos as usize..], &ctx)).await {
Err(e) if e.any(|e| *e == ekey_na) => continue,
Err(e) => {
let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api()));
expr_store.dispose().await;
return handle.reply(&lex, &eopt).await;
},
Ok((s, expr)) => {
let expr = expr.into_api(&mut (), &mut ()).await;
let pos = (text.len() - s.len()) as u32;
expr_store.dispose().await;
return handle.reply(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await;
},
}
}
writeln!(logger, "Got notified about n/a character '{trigger_char}'");
expr_store.dispose().await;
handle.reply(&lex, &None).await
})
.await,
api::HostExtReq::ParseLine(pline) => {
let api::ParseLine { module, src, exported, comments, sys, line, idx } = &pline;
with_sys_record(*sys, async {
let parsers = cted().inst().dyn_parsers();
let src = Sym::from_api(*src).await;
let comments =
join_all(comments.iter().map(|c| Comment::from_api(c, src.clone()))).await;
let expr_store = BorrowedExprStore::new();
let line: Vec<PTokTree> =
ttv_from_api(line, &mut &expr_store, &mut (), &src).await;
let snip = Snippet::new(line.first().expect("Empty line"), &line);
let parser = parsers[*idx as usize];
let module = Sym::from_api(*module).await;
let pctx = ParsCtx::new(module);
let o_line =
match try_with_reporter(parser.parse(pctx, *exported, comments, snip)).await {
Err(e) => Err(e.to_api()),
Ok(t) => Ok(linev_into_api(t).await),
};
mem::drop(line);
expr_store.dispose().await;
handle.reply(&pline, &o_line).await
})
.await
},
api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) =>
with_sys_record(sys, async {
let cnst = get_const(id).await;
handle.reply(fpc, &cnst.serialize().await).await
})
.await,
api::HostExtReq::AtomReq(atom_req) => {
let atom = atom_req.get_atom();
with_sys_record(atom.owner, async {
let (nfo, id, buf) = resolve_atom_type(atom);
let actx = AtomCtx(buf, atom.drop);
match &atom_req {
api::AtomReq::SerializeAtom(ser) => {
let mut buf = enc_vec(&id);
match nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await {
None => handle.reply(ser, &None).await,
Some(refs) => {
let refs =
join_all(refs.into_iter().map(async |ex| ex.into_api(&mut ()).await))
.await;
handle.reply(ser, &Some((buf, refs))).await
},
}
},
Ok((s, expr)) => {
let expr = expr.into_api(&mut (), &mut ()).await;
let pos = (text.len() - s.len()) as u32;
api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) =>
handle.reply(print, &nfo.print(actx).await.to_api()).await,
api::AtomReq::Fwded(fwded) => {
let api::Fwded(_, key, payload) = &fwded;
let mut reply = Vec::new();
let key = Sym::from_api(*key).await;
let some = nfo
.handle_req(
actx,
key,
Pin::<&mut &[u8]>::new(&mut &payload[..]),
Pin::<&mut Vec<_>>::new(&mut reply),
)
.await;
handle.reply(fwded, &some.then_some(reply)).await
},
api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => {
let expr_store = BorrowedExprStore::new();
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
let ret = nfo.call_ref(actx, Expr::from_handle(expr_handle.clone())).await;
let api_expr = ret.serialize().await;
mem::drop(expr_handle);
expr_store.dispose().await;
return hand.handle(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await;
handle.reply(call, &api_expr).await
},
api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => {
let expr_store = BorrowedExprStore::new();
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
let ret = nfo.call(actx, Expr::from_handle(expr_handle.clone())).await;
let api_expr = ret.serialize().await;
mem::drop(expr_handle);
expr_store.dispose().await;
handle.reply(call, &api_expr).await
},
api::AtomReq::Command(cmd @ api::Command(_)) => match nfo.command(actx).await {
Err(e) => handle.reply(cmd, &Err(e.to_api())).await,
Ok(opt) => match opt {
None => handle.reply(cmd, &Ok(api::NextStep::Halt)).await,
Some(cont) => {
let cont = cont.serialize().await;
handle.reply(cmd, &Ok(api::NextStep::Continue(cont))).await
},
},
},
}
}
writeln!(logger, "Got notified about n/a character '{trigger_char}'");
expr_store.dispose().await;
hand.handle(&lex, &None).await
})
.await,
api::HostExtReq::ParseLine(pline) => {
let api::ParseLine { module, src, exported, comments, sys, line, idx } = &pline;
with_ctx(get_ctx(*sys).await, async {
let parsers = ctx().cted().inst().dyn_parsers();
let src = Sym::from_api(*src, &i()).await;
let comments =
join_all(comments.iter().map(|c| Comment::from_api(c, src.clone(), &interner)))
.await;
let expr_store = BorrowedExprStore::new();
let line: Vec<PTokTree> =
ttv_from_api(line, &mut &expr_store, &mut (), &src, &i()).await;
let snip = Snippet::new(line.first().expect("Empty line"), &line);
let parser = parsers[*idx as usize];
let module = Sym::from_api(*module, &i()).await;
let reporter = Reporter::new();
let pctx = ParsCtx::new(module, &reporter);
let parse_res = parser.parse(pctx, *exported, comments, snip).await;
let o_line = match reporter.merge(parse_res) {
Err(e) => Err(e.to_api()),
Ok(t) => Ok(linev_into_api(t).await),
};
mem::drop(line);
expr_store.dispose().await;
hand.handle(&pline, &o_line).await
})
.await
},
api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) =>
with_ctx(get_ctx(sys).await, async move {
let cnst = get_const(id).await;
hand.handle(fpc, &cnst.serialize().await).await
})
.await,
api::HostExtReq::AtomReq(atom_req) => {
let atom = atom_req.get_atom();
let atom_req = atom_req.clone();
with_atom_record(&get_ctx, atom, async move |nfo, id, buf| {
let actx = AtomCtx(buf, atom.drop);
match &atom_req {
api::AtomReq::SerializeAtom(ser) => {
let mut buf = enc_vec(&id).await;
match nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await {
None => hand.handle(ser, &None).await,
Some(refs) => {
let refs =
join_all(refs.into_iter().map(|ex| async { ex.into_api(&mut ()).await }))
.await;
hand.handle(ser, &Some((buf, refs))).await
},
}
},
api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) =>
hand.handle(print, &nfo.print(actx).await.to_api()).await,
api::AtomReq::Fwded(fwded) => {
let api::Fwded(_, key, payload) = &fwded;
let mut reply = Vec::new();
let key = Sym::from_api(*key, &interner).await;
let some = nfo
.handle_req(
actx,
key,
Pin::<&mut &[u8]>::new(&mut &payload[..]),
Pin::<&mut Vec<_>>::new(&mut reply),
)
.await;
hand.handle(fwded, &some.then_some(reply)).await
},
api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => {
let expr_store = BorrowedExprStore::new();
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
let ret = nfo.call_ref(actx, Expr::from_handle(expr_handle.clone())).await;
let api_expr = ret.serialize().await;
mem::drop(expr_handle);
expr_store.dispose().await;
hand.handle(call, &api_expr).await
},
api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => {
let expr_store = BorrowedExprStore::new();
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
let ret = nfo.call(actx, Expr::from_handle(expr_handle.clone())).await;
let api_expr = ret.serialize().await;
mem::drop(expr_handle);
expr_store.dispose().await;
hand.handle(call, &api_expr).await
},
api::AtomReq::Command(cmd @ api::Command(_)) => match nfo.command(actx).await {
Err(e) => hand.handle(cmd, &Err(e.to_api())).await,
Ok(opt) => match opt {
None => hand.handle(cmd, &Ok(api::NextStep::Halt)).await,
Some(cont) => {
let cont = cont.serialize().await;
hand.handle(cmd, &Ok(api::NextStep::Continue(cont))).await
},
},
},
}
})
.await
},
api::HostExtReq::DeserAtom(deser) => {
let api::DeserAtom(sys, buf, refs) = &deser;
let mut read = &mut &buf[..];
let ctx = get_ctx(*sys).await;
// SAFETY: deserialization implicitly grants ownership to previously owned exprs
let refs = (refs.iter())
.map(|tk| Expr::from_handle(ExprHandle::deserialize(*tk)))
.collect_vec();
let id = AtomTypeId::decode(Pin::new(&mut read)).await;
let inst = ctx.cted().inst();
let nfo = atom_by_idx(inst.card(), id).expect("Deserializing atom with invalid ID");
hand.handle(&deser, &nfo.deserialize(read, &refs).await).await
},
}
}
.boxed_local()
}
},
);
*interner_cell.borrow_mut() =
Some(Interner::new_replica(rn.clone().map(|ir: api::IntReq| ir.into_root())));
spawner(Box::pin(clone!(spawner; async move {
let mut streams = stream_select! { in_recv.map(Some), exit_recv.map(|_| None) };
while let Some(item) = streams.next().await {
match item {
Some(rcvd) => spawner(Box::pin(clone!(rn; async move { rn.receive(&rcvd[..]).await }))),
None => break,
}
}
})));
ExtInit {
header: ext_header,
port: Box::new(ExtensionOwner {
out_recv: Mutex::new(out_recv),
out_send,
_interner_cell: interner_cell,
_systems_lock: systems_lock,
}),
})
.await
},
api::HostExtReq::DeserAtom(deser) => {
let api::DeserAtom(sys, buf, refs) = &deser;
let read = &mut &buf[..];
with_sys_record(*sys, async {
// SAFETY: deserialization implicitly grants ownership to previously owned exprs
let refs = (refs.iter())
.map(|tk| Expr::from_handle(ExprHandle::deserialize(*tk)))
.collect_vec();
let id = AtomTypeId::decode_slice(read);
let nfo = atom_by_idx(cted().inst().card(), id)
.expect("Deserializing atom with invalid ID");
handle.reply(&deser, &nfo.deserialize(read, &refs).await).await
})
.await
},
}
})
.await
},
);
// add essential services to the very tail, then fold all context into the run
// future
SYSTEM_TABLE
.scope(
RefCell::default(),
with_interner(
new_interner(),
with_logger(
logger2,
with_comm(
Rc::new(client),
ctx,
(self.context.into_iter()).fold(
Box::pin(async move { run_extension.await.unwrap() }) as LocalBoxFuture<()>,
|fut, cx| cx.apply(fut),
),
),
),
),
)
.await
}) as Pin<Box<_>>);
}
}

View File

@@ -10,12 +10,13 @@ use hashbrown::HashSet;
use orchid_base::error::OrcErrv;
use orchid_base::format::{FmtCtx, FmtUnit, Format};
use orchid_base::location::Pos;
use orchid_base::reqnot::Requester;
use orchid_base::stash::stash;
use crate::api;
use crate::atom::ForeignAtom;
use crate::context::{ctx, i};
use crate::entrypoint::{notify, request};
use crate::gen_expr::{GExpr, GExprKind};
use crate::system::sys_id;
pub struct BorrowedExprStore(RefCell<Option<HashSet<Rc<ExprHandle>>>>);
impl BorrowedExprStore {
@@ -73,7 +74,7 @@ impl ExprHandle {
/// to lend the expr, and you expect the receiver to use
/// [ExprHandle::borrowed] or [ExprHandle::from_ticket]
pub fn ticket(&self) -> api::ExprTicket { self.0 }
async fn send_acq(&self) { ctx().reqnot().notify(api::Acquire(ctx().sys_id(), self.0)).await }
async fn send_acq(&self) { notify(api::Acquire(sys_id(), self.0)).await }
/// If this is the last one reference, do nothing, otherwise send an Acquire
pub async fn on_borrow_expire(self: Rc<Self>) { self.serialize().await; }
/// Drop the handle and get the ticket without a release notification.
@@ -94,8 +95,8 @@ impl fmt::Debug for ExprHandle {
}
impl Drop for ExprHandle {
fn drop(&mut self) {
let notif = api::Release(ctx().sys_id(), self.0);
ctx().spawn(async move { ctx().reqnot().clone().notify(notif).await })
let notif = api::Release(sys_id(), self.0);
stash(async move { notify(notif).await })
}
}
@@ -117,12 +118,12 @@ impl Expr {
}
pub async fn data(&self) -> &ExprData {
(self.data.get_or_init(async {
let details = ctx().reqnot().request(api::Inspect { target: self.handle.ticket() }).await;
let pos = Pos::from_api(&details.location, &i()).await;
let details = request(api::Inspect { target: self.handle.ticket() }).await;
let pos = Pos::from_api(&details.location).await;
let kind = match details.kind {
api::InspectedKind::Atom(a) =>
ExprKind::Atom(ForeignAtom::new(self.handle.clone(), a, pos.clone())),
api::InspectedKind::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(&b, &i()).await),
api::InspectedKind::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(&b).await),
api::InspectedKind::Opaque => ExprKind::Opaque,
};
ExprData { pos, kind }
@@ -150,8 +151,7 @@ impl Format for Expr {
match &self.data().await.kind {
ExprKind::Opaque => "OPAQUE".to_string().into(),
ExprKind::Bottom(b) => format!("Bottom({b})").into(),
ExprKind::Atom(a) =>
FmtUnit::from_api(&ctx().reqnot().request(api::ExtAtomPrint(a.atom.clone())).await),
ExprKind::Atom(a) => FmtUnit::from_api(&request(api::ExtAtomPrint(a.atom.clone())).await),
}
}
}

View File

@@ -0,0 +1,12 @@
use std::pin::Pin;
use std::rc::Rc;
use futures::future::LocalBoxFuture;
use futures::{AsyncRead, AsyncWrite};
pub struct ExtPort {
pub input: Pin<Box<dyn AsyncRead>>,
pub output: Pin<Box<dyn AsyncWrite>>,
pub log: Pin<Box<dyn AsyncWrite>>,
pub spawn: Rc<dyn Fn(LocalBoxFuture<'static, ()>)>,
}

View File

@@ -1,12 +1,12 @@
use std::any::TypeId;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use futures::future::LocalBoxFuture;
use futures::lock::Mutex;
use futures::{AsyncWrite, FutureExt};
use itertools::Itertools;
use never::Never;
@@ -15,15 +15,17 @@ use orchid_base::clone;
use orchid_base::error::OrcRes;
use orchid_base::format::{FmtCtx, FmtUnit};
use orchid_base::name::Sym;
use task_local::task_local;
use trait_set::trait_set;
use crate::api;
use crate::atom::Atomic;
use crate::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant};
use crate::context::{SysCtxEntry, ctx, i};
use crate::conv::ToExpr;
use crate::coroutine_exec::{ExecHandle, exec};
use crate::expr::Expr;
use crate::gen_expr::GExpr;
use crate::system::sys_id;
trait_set! {
trait FunCB = Fn(Vec<Expr>) -> LocalBoxFuture<'static, OrcRes<GExpr>> + 'static;
@@ -34,9 +36,14 @@ pub trait ExprFunc<I, O>: Clone + 'static {
fn apply<'a>(&self, hand: ExecHandle<'a>, v: Vec<Expr>) -> impl Future<Output = OrcRes<GExpr>>;
}
#[derive(Default)]
struct FunsCtx(Mutex<HashMap<Sym, FunRecord>>);
impl SysCtxEntry for FunsCtx {}
task_local! {
static FUNS_CTX: RefCell<HashMap<(api::SysId, Sym), FunRecord>>;
}
pub(crate) fn with_funs_ctx<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
Box::pin(FUNS_CTX.scope(RefCell::default(), fut))
}
#[derive(Clone)]
struct FunRecord {
argtyps: &'static [TypeId],
@@ -77,17 +84,17 @@ pub(crate) struct Fun {
}
impl Fun {
pub async fn new<I, O, F: ExprFunc<I, O>>(path: Sym, f: F) -> Self {
let ctx = ctx();
let funs: &FunsCtx = ctx.get_or_default();
let mut fung = funs.0.lock().await;
let record = if let Some(record) = fung.get(&path) {
record.clone()
} else {
let record = process_args(f);
fung.insert(path.clone(), record.clone());
record
};
Self { args: vec![], path, record }
FUNS_CTX.with(|cx| {
let mut fung = cx.borrow_mut();
let record = if let Some(record) = fung.get(&(sys_id(), path.clone())) {
record.clone()
} else {
let record = process_args(f);
fung.insert((sys_id(), path.clone()), record.clone());
record
};
Self { args: vec![], path, record }
})
}
pub fn arity(&self) -> u8 { self.record.argtyps.len() as u8 }
}
@@ -108,12 +115,12 @@ impl OwnedAtom for Fun {
}
async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await }
async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs {
self.path.to_api().encode(write).await;
self.path.to_api().encode(write).await.unwrap();
self.args.clone()
}
async fn deserialize(mut ds_cx: impl DeserializeCtx, args: Self::Refs) -> Self {
let path = Sym::from_api(ds_cx.decode().await, &i()).await;
let record = (ctx().get::<FunsCtx>().0.lock().await.get(&path))
let path = Sym::from_api(ds_cx.decode().await).await;
let record = (FUNS_CTX.with(|funs| funs.borrow().get(&(sys_id(), path.clone())).cloned()))
.expect("Function missing during deserialization")
.clone();
Self { args, path, record }

View File

@@ -6,12 +6,11 @@ use orchid_base::error::{OrcErr, OrcErrv};
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
use orchid_base::location::Pos;
use orchid_base::name::Sym;
use orchid_base::reqnot::Requester;
use orchid_base::{match_mapping, tl_cache};
use crate::api;
use crate::atom::{AtomFactory, ToAtom};
use crate::context::ctx;
use crate::entrypoint::request;
use crate::expr::Expr;
#[derive(Clone, Debug)]
@@ -40,7 +39,7 @@ impl GExpr {
}
pub fn at(self, pos: Pos) -> Self { GExpr { pos, kind: self.kind } }
pub async fn create(self) -> Expr {
Expr::deserialize(ctx().reqnot().request(api::Create(self.serialize().await)).await).await
Expr::deserialize(request(api::Create(self.serialize().await)).await).await
}
}
impl Format for GExpr {

View File

@@ -1,73 +1,48 @@
use std::borrow::Borrow;
use std::cell::RefCell;
use std::fmt::Debug;
use std::hash::Hash;
use std::rc::{Rc, Weak};
use std::rc::Rc;
use hashbrown::HashMap;
use orchid_api_traits::Coding;
use orchid_base::interner::{IStr, IStrHandle, IStrv, IStrvHandle};
use futures::future::{LocalBoxFuture, join_all, ready};
use itertools::Itertools;
use orchid_base::interner::local_interner::{Int, StrBranch, StrvBranch};
use orchid_base::interner::{IStr, IStrv, InternerSrv};
use crate::api;
use crate::entrypoint::request;
trait Branch: 'static {
type Token: Clone + Copy + Debug + Hash + PartialEq + Eq + PartialOrd + Ord + Coding + 'static;
type Data: 'static + Borrow<Self::Borrow>;
type Borrow: ToOwned<Owned = Self::Data> + ?Sized;
type Handle: AsRef<Self::Borrow>;
type Interned: Clone;
fn mk_interned(t: Self::Token, h: Rc<Self::Handle>) -> Self::Interned;
#[derive(Default)]
struct ExtInterner {
str: Int<StrBranch>,
strv: Int<StrvBranch>,
}
impl InternerSrv for ExtInterner {
fn is<'a>(&'a self, v: &'a str) -> LocalBoxFuture<'a, IStr> {
match self.str.i(v) {
Ok(i) => Box::pin(ready(i)),
Err(e) => Box::pin(async { e.set_if_empty(request(api::InternStr(v.to_owned())).await) }),
}
}
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr> {
match self.str.e(t) {
Ok(i) => Box::pin(ready(i)),
Err(e) => Box::pin(async move { e.set_if_empty(Rc::new(request(api::ExternStr(t)).await)) }),
}
}
fn iv<'a>(&'a self, v: &'a [IStr]) -> LocalBoxFuture<'a, IStrv> {
match self.strv.i(v) {
Ok(i) => Box::pin(ready(i)),
Err(e) => Box::pin(async {
e.set_if_empty(request(api::InternStrv(v.iter().map(|is| is.to_api()).collect_vec())).await)
}),
}
}
fn ev(&self, t: orchid_api::TStrv) -> LocalBoxFuture<'_, IStrv> {
match self.strv.e(t) {
Ok(i) => Box::pin(ready(i)),
Err(e) => Box::pin(async move {
let tstr_v = request(api::ExternStrv(t)).await;
e.set_if_empty(Rc::new(join_all(tstr_v.into_iter().map(|t| self.es(t))).await))
}),
}
}
}
struct StrBranch;
impl Branch for StrBranch {
type Data = String;
type Token = api::TStr;
type Borrow = str;
type Handle = Handle<Self>;
type Interned = IStr;
fn mk_interned(t: Self::Token, h: Rc<Self::Handle>) -> Self::Interned { IStr(t, h) }
}
struct StrvBranch;
impl Branch for StrvBranch {
type Data = Vec<IStr>;
type Token = api::TStrv;
type Borrow = [IStr];
type Handle = Handle<Self>;
type Interned = IStrv;
fn mk_interned(t: Self::Token, h: Rc<Self::Handle>) -> Self::Interned { IStrv(t, h) }
}
struct Data<B: Branch> {
token: B::Token,
data: Rc<B::Data>,
}
struct Handle<B: Branch> {
data: Rc<Data<B>>,
parent: Weak<RefCell<IntData<B>>>,
}
impl IStrHandle for Handle<StrBranch> {}
impl AsRef<str> for Handle<StrBranch> {
fn as_ref(&self) -> &str { self.data.data.as_ref().as_ref() }
}
impl IStrvHandle for Handle<StrvBranch> {}
impl AsRef<[IStr]> for Handle<StrvBranch> {
fn as_ref(&self) -> &[IStr] { self.data.data.as_ref().as_ref() }
}
struct Rec<B: Branch> {
handle: Weak<B::Handle>,
data: Rc<Data<B>>,
}
struct IntData<B: Branch> {
by_tok: HashMap<B::Token, Rec<B>>,
by_data: HashMap<Rc<B::Data>, Rec<B>>,
}
impl<B: Branch> IntData<B> {
async fn i(&mut self, q: &B::Borrow) -> B::Interned { todo!() }
async fn e(&mut self, q: &B::Token) -> B::Interned { todo!() }
}
struct Int<B: Branch>(Rc<RefCell<IntData<B>>>);
pub fn new_interner() -> Rc<dyn InternerSrv> { Rc::<ExtInterner>::default() }

View File

@@ -1,27 +1,24 @@
use std::fmt;
use std::fmt::Debug;
use std::future::Future;
use std::ops::RangeInclusive;
use futures::FutureExt;
use futures::future::LocalBoxFuture;
use orchid_base::error::{OrcErrv, OrcRes, Reporter, mk_errv};
use orchid_base::interner::{Interner, Tok};
use orchid_base::error::{OrcErrv, OrcRes, mk_errv};
use orchid_base::interner::{IStr, is};
use orchid_base::location::{Pos, SrcRange};
use orchid_base::name::Sym;
use orchid_base::parse::ParseCtx;
use orchid_base::reqnot::Requester;
use crate::api;
use crate::context::{ctx, i};
use crate::entrypoint::request;
use crate::expr::BorrowedExprStore;
use crate::parser::PTokTree;
use crate::tree::GenTokTree;
pub async fn ekey_cascade() -> Tok<String> {
i().i("An error cascading from a recursive call").await
}
pub async fn ekey_not_applicable() -> Tok<String> {
i().i("Pseudo-error to communicate that the current branch in a dispatch doesn't apply").await
pub async fn ekey_cascade() -> IStr { is("An error cascading from a recursive call").await }
pub async fn ekey_not_applicable() -> IStr {
is("Pseudo-error to communicate that the current branch in a dispatch doesn't apply").await
}
const MSG_INTERNAL_ERROR: &str = "This error is a sentinel for the extension library.\
it should not be emitted by the extension.";
@@ -36,23 +33,20 @@ pub async fn err_not_applicable() -> OrcErrv {
pub struct LexContext<'a> {
pub(crate) exprs: &'a BorrowedExprStore,
pub text: &'a Tok<String>,
pub text: &'a IStr,
pub id: api::ParsId,
pub pos: u32,
i: Interner,
pub(crate) src: Sym,
pub(crate) rep: &'a Reporter,
}
impl<'a> LexContext<'a> {
pub fn new(
exprs: &'a BorrowedExprStore,
text: &'a Tok<String>,
text: &'a IStr,
id: api::ParsId,
pos: u32,
src: Sym,
rep: &'a Reporter,
) -> Self {
Self { exprs, i: i(), id, pos, rep, src, text }
Self { exprs, id, pos, src, text }
}
pub fn src(&self) -> &Sym { &self.src }
/// This function returns [PTokTree] because it can never return
@@ -61,10 +55,10 @@ impl<'a> LexContext<'a> {
/// for embedding in the return value.
pub async fn recurse(&self, tail: &'a str) -> OrcRes<(&'a str, PTokTree)> {
let start = self.pos(tail);
let Some(lx) = ctx().reqnot().request(api::SubLex { pos: start, id: self.id }).await else {
let Some(lx) = request(api::SubLex { pos: start, id: self.id }).await else {
return Err(err_cascade().await);
};
let tree = PTokTree::from_api(&lx.tree, &mut { self.exprs }, &mut (), &self.src, &i()).await;
let tree = PTokTree::from_api(&lx.tree, &mut { self.exprs }, &mut (), &self.src).await;
Ok((&self.text[lx.pos as usize..], tree))
}
@@ -77,12 +71,8 @@ impl<'a> LexContext<'a> {
SrcRange::new(self.pos(tail) - len.try_into().unwrap()..self.pos(tail), &self.src)
}
}
impl ParseCtx for LexContext<'_> {
fn i(&self) -> &Interner { &self.i }
fn rep(&self) -> &Reporter { self.rep }
}
pub trait Lexer: Send + Sync + Sized + Default + 'static {
pub trait Lexer: Debug + Send + Sync + Sized + Default + 'static {
const CHAR_FILTER: &'static [RangeInclusive<char>];
fn lex<'a>(
tail: &'a str,
@@ -90,7 +80,7 @@ pub trait Lexer: Send + Sync + Sized + Default + 'static {
) -> impl Future<Output = OrcRes<(&'a str, GenTokTree)>>;
}
pub trait DynLexer: Send + Sync + 'static {
pub trait DynLexer: Debug + Send + Sync + 'static {
fn char_filter(&self) -> &'static [RangeInclusive<char>];
fn lex<'a>(
&self,

View File

@@ -7,12 +7,11 @@ pub mod conv;
pub mod coroutine_exec;
pub mod entrypoint;
pub mod expr;
pub mod ext_port;
pub mod func_atom;
pub mod gen_expr;
pub mod lexer;
// pub mod msg;
pub mod context;
pub mod interner;
pub mod lexer;
pub mod other_system;
pub mod parser;
pub mod reflection;

View File

@@ -1,6 +1,7 @@
use crate::api;
use crate::system::{DynSystemCard, SystemCard};
#[derive(Debug)]
pub struct SystemHandle<C: SystemCard> {
pub(crate) card: C,
pub(crate) id: api::SysId,

View File

@@ -5,21 +5,22 @@ use futures::future::{LocalBoxFuture, join_all};
use futures::{FutureExt, Stream, StreamExt};
use itertools::Itertools;
use never::Never;
use orchid_base::error::{OrcErrv, OrcRes, Reporter};
use orchid_base::error::{OrcErrv, OrcRes};
use orchid_base::id_store::IdStore;
use orchid_base::interner::{Interner, Tok};
use orchid_base::interner::IStr;
use orchid_base::location::SrcRange;
use orchid_base::match_mapping;
use orchid_base::name::Sym;
use orchid_base::parse::{Comment, ParseCtx, Snippet};
use orchid_base::reqnot::Requester;
use orchid_base::parse::{Comment, Snippet};
use orchid_base::tree::{TokTree, Token, ttv_into_api};
use task_local::task_local;
use crate::api;
use crate::context::{SysCtxEntry, ctx, i};
use crate::conv::ToExpr;
use crate::entrypoint::request;
use crate::expr::Expr;
use crate::gen_expr::GExpr;
use crate::system::sys_id;
use crate::tree::{GenTok, GenTokTree};
pub type PTok = Token<Expr, Never>;
@@ -82,27 +83,21 @@ pub type ParserObj = &'static dyn DynParser;
pub struct ParsCtx<'a> {
_parse: PhantomData<&'a mut ()>,
module: Sym,
reporter: &'a Reporter,
i: Interner,
}
impl<'a> ParsCtx<'a> {
pub(crate) fn new(module: Sym, reporter: &'a Reporter) -> Self {
Self { _parse: PhantomData, module, reporter, i: i() }
}
pub(crate) fn new(module: Sym) -> Self { Self { _parse: PhantomData, module } }
pub fn module(&self) -> Sym { self.module.clone() }
}
impl ParseCtx for ParsCtx<'_> {
fn i(&self) -> &Interner { &self.i }
fn rep(&self) -> &Reporter { self.reporter }
}
type BoxConstCallback = Box<dyn FnOnce(ConstCtx) -> LocalBoxFuture<'static, GExpr>>;
#[derive(Default)]
pub(crate) struct ParsedConstCtxEntry {
pub(crate) consts: IdStore<BoxConstCallback>,
task_local! {
static PARSED_CONST_CTX: IdStore<BoxConstCallback>
}
pub(crate) fn with_parsed_const_ctx<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
Box::pin(PARSED_CONST_CTX.scope(IdStore::default(), fut))
}
impl SysCtxEntry for ParsedConstCtxEntry {}
pub struct ParsedLine {
pub sr: SrcRange,
@@ -114,7 +109,7 @@ impl ParsedLine {
sr: &SrcRange,
comments: impl IntoIterator<Item = &'a Comment>,
exported: bool,
name: Tok<String>,
name: IStr,
f: F,
) -> Self {
let cb = Box::new(|ctx| async move { f(ctx).await.to_gen().await }.boxed_local());
@@ -126,7 +121,7 @@ impl ParsedLine {
sr: &SrcRange,
comments: impl IntoIterator<Item = &'a Comment>,
exported: bool,
name: &Tok<String>,
name: &IStr,
use_prelude: bool,
lines: impl IntoIterator<Item = ParsedLine>,
) -> Self {
@@ -145,7 +140,7 @@ impl ParsedLine {
exported: mem.exported,
kind: match mem.kind {
ParsedMemKind::Const(cb) => api::ParsedMemberKind::Constant(api::ParsedConstId(
ctx().get_or_default::<ParsedConstCtxEntry>().consts.add(cb).id(),
PARSED_CONST_CTX.with(|consts| consts.add(cb).id()),
)),
ParsedMemKind::Mod { lines, use_prelude } => api::ParsedMemberKind::Module {
lines: linev_into_api(lines).boxed_local().await,
@@ -170,7 +165,7 @@ pub enum ParsedLineKind {
}
pub struct ParsedMem {
pub name: Tok<String>,
pub name: IStr,
pub exported: bool,
pub kind: ParsedMemKind,
}
@@ -191,14 +186,14 @@ impl ConstCtx {
) -> impl Stream<Item = OrcRes<Sym>> + 'b {
let resolve_names = api::ResolveNames {
constid: self.constid,
sys: ctx().sys_id(),
sys: sys_id(),
names: names.into_iter().map(|n| n.to_api()).collect_vec(),
};
stream(async |mut cx| {
for name_opt in ctx().reqnot().request(resolve_names).await {
for name_opt in request(resolve_names).await {
cx.emit(match name_opt {
Err(e) => Err(OrcErrv::from_api(&e, &i()).await),
Ok(name) => Ok(Sym::from_api(name, &i()).await),
Err(e) => Err(OrcErrv::from_api(&e).await),
Ok(name) => Ok(Sym::from_api(name).await),
})
.await
}
@@ -210,8 +205,7 @@ impl ConstCtx {
}
pub(crate) async fn get_const(id: api::ParsedConstId) -> GExpr {
let cb = (ctx().get_or_default::<ParsedConstCtxEntry>().consts.get(id.0))
.expect("Bad ID or double read of parsed const")
.remove();
let cb = PARSED_CONST_CTX
.with(|ent| ent.get(id.0).expect("Bad ID or double read of parsed const").remove());
cb(ConstCtx { constid: id }).await
}

View File

@@ -1,15 +1,18 @@
use std::cell::OnceCell;
use std::cell::{OnceCell, RefCell};
use std::rc::Rc;
use futures::FutureExt;
use futures::future::LocalBoxFuture;
use futures::lock::Mutex;
use hashbrown::HashMap;
use memo_map::MemoMap;
use orchid_base::interner::Tok;
use orchid_base::interner::{IStr, es, iv};
use orchid_base::name::{NameLike, VPath};
use orchid_base::reqnot::Requester;
use task_local::task_local;
use crate::api;
use crate::context::{SysCtxEntry, ctx, i};
use crate::entrypoint::request;
use crate::system::sys_id;
#[derive(Debug)]
pub struct ReflMemData {
@@ -33,29 +36,28 @@ pub enum ReflMemKind {
pub struct ReflModData {
inferred: Mutex<bool>,
path: VPath,
members: MemoMap<Tok<String>, ReflMem>,
members: MemoMap<IStr, ReflMem>,
}
#[derive(Clone, Debug)]
pub struct ReflMod(Rc<ReflModData>);
impl ReflMod {
pub fn path(&self) -> &[Tok<String>] { &self.0.path[..] }
pub fn path(&self) -> &[IStr] { &self.0.path[..] }
pub fn is_root(&self) -> bool { self.0.path.is_empty() }
async fn try_populate(&self) -> Result<(), api::LsModuleError> {
let path_tok = i().i(&self.0.path[..]).await;
let reply = match ctx().reqnot().request(api::LsModule(ctx().sys_id(), path_tok.to_api())).await
{
let path_tok = iv(&self.0.path[..]).await;
let reply = match request(api::LsModule(sys_id(), path_tok.to_api())).await {
Err(api::LsModuleError::TreeUnavailable) =>
panic!("Reflected tree accessed outside an interpreter call. This extension is faulty."),
Err(err) => return Err(err),
Ok(details) => details,
};
for (k, v) in reply.members {
let k = i().ex(k).await;
let k = es(k).await;
let mem = match self.0.members.get(&k) {
Some(mem) => mem,
None => {
let path = self.0.path.clone().name_with_suffix(k.clone()).to_sym(&i()).await;
let path = self.0.path.clone().name_with_suffix(k.clone()).to_sym().await;
let kind = match v.kind {
api::MemberInfoKind::Constant => ReflMemKind::Const,
api::MemberInfoKind::Module =>
@@ -68,7 +70,7 @@ impl ReflMod {
}
Ok(())
}
pub async fn get_child(&self, key: &Tok<String>) -> Option<ReflMem> {
pub async fn get_child(&self, key: &IStr) -> Option<ReflMem> {
let inferred_g = self.0.inferred.lock().await;
if let Some(mem) = self.0.members.get(key) {
return Some(mem.clone());
@@ -86,7 +88,7 @@ impl ReflMod {
}
self.0.members.get(key).cloned()
}
pub async fn get_by_path(&self, path: &[Tok<String>]) -> Result<ReflMem, InvalidPathError> {
pub async fn get_by_path(&self, path: &[IStr]) -> Result<ReflMem, InvalidPathError> {
let (next, tail) = path.split_first().expect("Attempted to walk by empty path");
let inferred_g = self.0.inferred.lock().await;
if let Some(next) = self.0.members.get(next) {
@@ -130,9 +132,9 @@ impl ReflMod {
}
}
#[derive(Clone)]
struct ReflRoot(ReflMod);
impl SysCtxEntry for ReflRoot {}
task_local! {
static REFL_ROOTS: RefCell<HashMap<api::SysId, ReflMod>>
}
#[derive(Clone, Debug)]
pub struct InvalidPathError {
@@ -150,8 +152,12 @@ fn default_member(is_root: bool, kind: ReflMemKind) -> ReflMem {
}))
}
fn get_root() -> ReflRoot {
ctx().get_or_insert(|| ReflRoot(default_module(VPath::new([])))).clone()
pub fn refl() -> ReflMod {
REFL_ROOTS.with(|tbl| {
tbl.borrow_mut().entry(sys_id()).or_insert_with(|| default_module(VPath::new([]))).clone()
})
}
pub fn refl() -> ReflMod { get_root().0.clone() }
pub fn with_refl_roots<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
Box::pin(REFL_ROOTS.scope(RefCell::default(), fut))
}

View File

@@ -1,4 +1,5 @@
use std::any::{Any, TypeId};
use std::fmt::Debug;
use std::future::Future;
use std::num::NonZero;
use std::pin::Pin;
@@ -8,13 +9,13 @@ use futures::future::LocalBoxFuture;
use orchid_api_traits::{Coding, Decode, Encode, Request};
use orchid_base::boxed_iter::BoxedIter;
use orchid_base::name::Sym;
use orchid_base::reqnot::{Receipt, Requester};
use orchid_base::reqnot::{Receipt, ReqHandle, ReqReader, ReqReaderExt};
use task_local::task_local;
use crate::api;
use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId, AtomicFeatures, ForeignAtom, TAtom, get_info};
use crate::context::ctx;
use crate::coroutine_exec::Replier;
use crate::entrypoint::ExtReq;
use crate::entrypoint::request;
use crate::func_atom::{Fun, Lambda};
use crate::lexer::LexerObj;
use crate::parser::ParserObj;
@@ -22,7 +23,7 @@ use crate::system_ctor::{CtedObj, SystemCtor};
use crate::tree::GenMember;
/// System as consumed by foreign code
pub trait SystemCard: Default + Send + Sync + 'static {
pub trait SystemCard: Debug + Default + Send + Sync + 'static {
type Ctor: SystemCtor;
type Req: Coding;
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn AtomDynfo>>>;
@@ -67,7 +68,7 @@ pub async fn resolv_atom(
sys: &(impl DynSystemCard + ?Sized),
atom: &api::Atom,
) -> Box<dyn AtomDynfo> {
let tid = AtomTypeId::decode(Pin::new(&mut &atom.data.0[..])).await;
let tid = AtomTypeId::decode(Pin::new(&mut &atom.data.0[..])).await.unwrap();
atom_by_idx(sys, tid).expect("Value of nonexistent type found")
}
@@ -84,7 +85,10 @@ pub trait System: Send + Sync + SystemCard + 'static {
fn env() -> impl Future<Output = Vec<GenMember>>;
fn lexers() -> Vec<LexerObj>;
fn parsers() -> Vec<ParserObj>;
fn request(hand: ExtReq<'_>, req: Self::Req) -> impl Future<Output = Receipt<'_>>;
fn request<'a>(
hand: Box<dyn ReqHandle<'a> + 'a>,
req: Self::Req,
) -> impl Future<Output = Receipt<'a>>;
}
pub trait DynSystem: Send + Sync + DynSystemCard + 'static {
@@ -92,7 +96,7 @@ pub trait DynSystem: Send + Sync + DynSystemCard + 'static {
fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>>;
fn dyn_lexers(&self) -> Vec<LexerObj>;
fn dyn_parsers(&self) -> Vec<ParserObj>;
fn dyn_request<'a>(&self, hand: ExtReq<'a>, req: Vec<u8>) -> LocalBoxFuture<'a, Receipt<'a>>;
fn dyn_request<'a>(&self, hand: Box<dyn ReqReader<'a> + 'a>) -> LocalBoxFuture<'a, Receipt<'a>>;
fn card(&self) -> &dyn DynSystemCard;
}
@@ -101,26 +105,41 @@ impl<T: System> DynSystem for T {
fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>> { Self::env().boxed_local() }
fn dyn_lexers(&self) -> Vec<LexerObj> { Self::lexers() }
fn dyn_parsers(&self) -> Vec<ParserObj> { Self::parsers() }
fn dyn_request<'a>(&self, hand: ExtReq<'a>, req: Vec<u8>) -> LocalBoxFuture<'a, Receipt<'a>> {
fn dyn_request<'a>(
&self,
mut hand: Box<dyn ReqReader<'a> + 'a>,
) -> LocalBoxFuture<'a, Receipt<'a>> {
Box::pin(async move {
Self::request(hand, <Self as SystemCard>::Req::decode(Pin::new(&mut &req[..])).await).await
let value = hand.read_req::<<Self as SystemCard>::Req>().await.unwrap();
Self::request(hand.finish().await, value).await
})
}
fn card(&self) -> &dyn DynSystemCard { self }
}
#[derive(Clone)]
pub(crate) struct SysCtx(pub api::SysId, pub CtedObj);
task_local! {
static SYS_CTX: SysCtx;
}
pub(crate) async fn with_sys<F: Future>(sys: SysCtx, fut: F) -> F::Output {
SYS_CTX.scope(sys, fut).await
}
pub fn sys_id() -> api::SysId { SYS_CTX.with(|cx| cx.0) }
pub fn cted() -> CtedObj { SYS_CTX.with(|cx| cx.1.clone()) }
pub async fn downcast_atom<A>(foreign: ForeignAtom) -> Result<TAtom<A>, ForeignAtom>
where A: AtomicFeatures {
let mut data = &foreign.atom.data.0[..];
let ctx = ctx();
let value = AtomTypeId::decode(Pin::new(&mut data)).await;
let own_inst = ctx.get::<CtedObj>().inst();
let owner = if *ctx.get::<api::SysId>() == foreign.atom.owner {
let value = AtomTypeId::decode_slice(&mut data);
let cted = cted();
let own_inst = cted.inst();
let owner = if sys_id() == foreign.atom.owner {
own_inst.card()
} else {
(ctx.get::<CtedObj>().deps().find(|s| s.id() == foreign.atom.owner))
.ok_or_else(|| foreign.clone())?
.get_card()
cted.deps().find(|s| s.id() == foreign.atom.owner).ok_or_else(|| foreign.clone())?.get_card()
};
if owner.atoms().flatten().all(|dynfo| dynfo.tid() != TypeId::of::<A>()) {
return Err(foreign);
@@ -130,22 +149,24 @@ where A: AtomicFeatures {
return Err(foreign);
}
let val = dynfo.decode(AtomCtx(data, foreign.atom.drop)).await;
let value = *val.downcast::<A::Data>().expect("atom decode returned wrong type");
Ok(TAtom { value, untyped: foreign })
let Ok(value) = val.downcast::<A::Data>() else {
panic!("decode of {} returned wrong type.", dynfo.name());
};
Ok(TAtom { value: *value, untyped: foreign })
}
pub async fn dep_req<Sys: SystemCard, Req: Request + Into<Sys::Req>>(req: Req) -> Req::Response {
let ctx = ctx();
let mut msg = Vec::new();
req.into().encode(std::pin::pin!(&mut msg)).await;
let own_inst = ctx.get::<CtedObj>().inst();
req.into().encode_vec(&mut msg);
let cted = cted();
let own_inst = cted.inst();
let owner = if own_inst.card().type_id() == TypeId::of::<Sys>() {
ctx.sys_id()
sys_id()
} else {
(ctx.get::<CtedObj>().deps().find(|s| s.get_card().type_id() == TypeId::of::<Sys>()))
(cted.deps().find(|s| s.get_card().type_id() == TypeId::of::<Sys>()))
.expect("System not in dependency array")
.id()
};
let reply = ctx.reqnot().request(api::SysFwd(owner, msg)).await;
Req::Response::decode(std::pin::pin!(&reply[..])).await
let reply = request(api::SysFwd(owner, msg)).await;
Req::Response::decode(std::pin::pin!(&reply[..])).await.unwrap()
}

View File

@@ -1,4 +1,5 @@
use std::any::Any;
use std::fmt::Debug;
use std::sync::Arc;
use orchid_base::boxed_iter::{BoxedIter, box_empty, box_once};
@@ -8,6 +9,7 @@ use crate::api;
use crate::other_system::{DynSystemHandle, SystemHandle};
use crate::system::{DynSystem, System, SystemCard};
#[derive(Debug)]
pub struct Cted<Ctor: SystemCtor + ?Sized> {
pub deps: <Ctor::Deps as DepDef>::Sat,
pub inst: Arc<Ctor::Instance>,
@@ -15,7 +17,7 @@ pub struct Cted<Ctor: SystemCtor + ?Sized> {
impl<C: SystemCtor + ?Sized> Clone for Cted<C> {
fn clone(&self) -> Self { Self { deps: self.deps.clone(), inst: self.inst.clone() } }
}
pub trait DynCted: Send + Sync + 'static {
pub trait DynCted: Debug + Send + Sync + 'static {
fn as_any(&self) -> &dyn Any;
fn deps<'a>(&'a self) -> BoxedIter<'a, &'a (dyn DynSystemHandle + 'a)>;
fn inst(&self) -> Arc<dyn DynSystem>;
@@ -27,11 +29,11 @@ impl<C: SystemCtor + ?Sized> DynCted for Cted<C> {
}
pub type CtedObj = Arc<dyn DynCted>;
pub trait DepSat: Clone + Send + Sync + 'static {
pub trait DepSat: Debug + Clone + Send + Sync + 'static {
fn iter<'a>(&'a self) -> BoxedIter<'a, &'a (dyn DynSystemHandle + 'a)>;
}
pub trait DepDef {
pub trait DepDef: Debug {
type Sat: DepSat;
fn report(names: &mut impl FnMut(&'static str));
fn create(take: &mut impl FnMut() -> api::SysId) -> Self::Sat;
@@ -57,17 +59,17 @@ impl DepDef for () {
fn report(_: &mut impl FnMut(&'static str)) {}
}
pub trait SystemCtor: Send + Sync + 'static {
pub trait SystemCtor: Debug + Send + Sync + 'static {
type Deps: DepDef;
type Instance: System;
const NAME: &'static str;
const VERSION: f64;
/// Create a system instance. When this function is called, a context object
/// isn't yet available
fn inst(deps: <Self::Deps as DepDef>::Sat) -> Self::Instance;
fn inst(&self, deps: <Self::Deps as DepDef>::Sat) -> Self::Instance;
}
pub trait DynSystemCtor: Send + Sync + 'static {
pub trait DynSystemCtor: Debug + Send + Sync + 'static {
fn decl(&self, id: api::SysDeclId) -> api::SystemDecl;
fn new_system(&self, new: &api::NewSystem) -> CtedObj;
}
@@ -84,7 +86,7 @@ impl<T: SystemCtor> DynSystemCtor for T {
fn new_system(&self, api::NewSystem { system: _, id: _, depends }: &api::NewSystem) -> CtedObj {
let mut ids = depends.iter().copied();
let deps = T::Deps::create(&mut || ids.next().unwrap());
let inst = Arc::new(T::inst(deps.clone()));
let inst = Arc::new(self.inst(deps.clone()));
Arc::new(Cted::<T> { deps, inst })
}
}
@@ -151,8 +153,4 @@ mod dep_set_tuple_impls {
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L); // 12
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); // 16
}

View File

@@ -1,57 +1,24 @@
use crate::entrypoint::ExtensionData;
use std::rc::Rc;
use crate::entrypoint::ExtensionBuilder;
use crate::ext_port::ExtPort;
#[cfg(feature = "tokio")]
pub async fn tokio_main(data: ExtensionData) {
use std::io::{ErrorKind, Write};
use std::mem;
use std::pin::{Pin, pin};
use std::rc::Rc;
use async_once_cell::OnceCell;
use futures::StreamExt;
use futures::future::LocalBoxFuture;
use futures::lock::Mutex;
use futures::stream::FuturesUnordered;
use orchid_api_traits::{Decode, Encode};
use orchid_base::msg::{recv_msg, send_msg};
use tokio::io::{Stdout, stdin, stdout};
pub async fn tokio_main(builder: ExtensionBuilder) {
use tokio::io::{stderr, stdin, stdout};
use tokio::task::{LocalSet, spawn_local};
use tokio_util::compat::{Compat, TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
use crate::api;
use crate::entrypoint::extension_init;
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
let local_set = LocalSet::new();
local_set.spawn_local(async {
let host_header = api::HostHeader::decode(Pin::new(&mut stdin().compat())).await;
let init =
Rc::new(extension_init(data, host_header, Rc::new(|fut| mem::drop(spawn_local(fut)))));
let mut buf = Vec::new();
init.header.encode(Pin::new(&mut buf)).await;
std::io::stdout().write_all(&buf).unwrap();
std::io::stdout().flush().unwrap();
// These are concurrent processes that never exit, so if the FuturesUnordered
// produces any result the extension should exit
let mut io = FuturesUnordered::<LocalBoxFuture<()>>::new();
io.push(Box::pin(async {
loop {
match recv_msg(pin!(stdin().compat())).await {
Ok(msg) => init.send(&msg[..]).await,
Err(e) if e.kind() == ErrorKind::BrokenPipe => break,
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
Err(e) => panic!("{e}"),
}
}
}));
io.push(Box::pin(async {
while let Some(msg) = init.recv().await {
static STDOUT: OnceCell<Mutex<Compat<Stdout>>> = OnceCell::new();
let stdout_lk = STDOUT.get_or_init(async { Mutex::new(stdout().compat_write()) }).await;
let mut stdout_g = stdout_lk.lock().await;
send_msg(pin!(&mut *stdout_g), &msg[..]).await.expect("Parent pipe broken");
}
}));
io.next().await;
builder.build(ExtPort {
input: Box::pin(stdin().compat()),
output: Box::pin(stdout().compat_write()),
log: Box::pin(stderr().compat_write()),
spawn: Rc::new(|fut| {
spawn_local(fut);
}),
});
});
local_set.await;
}

View File

@@ -1,4 +1,6 @@
use std::cell::RefCell;
use std::num::NonZero;
use std::rc::Rc;
use async_fn_stream::stream;
use dyn_clone::{DynClone, clone_box};
@@ -6,17 +8,16 @@ use futures::future::{LocalBoxFuture, join_all};
use futures::{FutureExt, StreamExt};
use hashbrown::HashMap;
use itertools::Itertools;
use orchid_base::interner::{Interner, Tok};
use orchid_base::interner::{IStr, is};
use orchid_base::location::SrcRange;
use orchid_base::name::Sym;
use orchid_base::tree::{TokTree, Token, TokenVariant};
use substack::Substack;
use task_local::task_local;
use trait_set::trait_set;
use crate::api;
use crate::context::i;
use crate::conv::ToExpr;
use crate::entrypoint::MemberRecord;
use crate::expr::{BorrowedExprStore, Expr, ExprHandle};
use crate::func_atom::{ExprFunc, Fun};
use crate::gen_expr::{GExpr, sym_ref};
@@ -27,12 +28,7 @@ pub type GenTok = Token<Expr, GExpr>;
impl TokenVariant<api::Expression> for GExpr {
type FromApiCtx<'a> = ();
type ToApiCtx<'a> = ();
async fn from_api(
_: &api::Expression,
_: &mut Self::FromApiCtx<'_>,
_: SrcRange,
_: &Interner,
) -> Self {
async fn from_api(_: &api::Expression, _: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
panic!("Received new expression from host")
}
async fn into_api(self, _: &mut Self::ToApiCtx<'_>) -> api::Expression { self.serialize().await }
@@ -40,12 +36,7 @@ impl TokenVariant<api::Expression> for GExpr {
impl TokenVariant<api::ExprTicket> for Expr {
type FromApiCtx<'a> = &'a BorrowedExprStore;
async fn from_api(
api: &api::ExprTicket,
exprs: &mut Self::FromApiCtx<'_>,
_: SrcRange,
_: &Interner,
) -> Self {
async fn from_api(api: &api::ExprTicket, exprs: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
// SAFETY: receiving trees from sublexers implies borrowing
Expr::from_handle(ExprHandle::borrowed(*api, exprs))
}
@@ -84,9 +75,8 @@ pub fn root_mod(name: &str, mems: impl IntoIterator<Item = Vec<GenMember>>) -> (
(name.to_string(), kind)
}
pub fn fun<I, O>(public: bool, name: &str, xf: impl ExprFunc<I, O>) -> Vec<GenMember> {
let fac = LazyMemberFactory::new(async move |sym| {
MemKind::Const(Fun::new(sym, xf).await.to_gen().await)
});
let fac =
LazyMemberFactory::new(async move |sym| MemKind::Const(Fun::new(sym, xf).await.to_gen().await));
vec![GenMember { name: name.to_string(), kind: MemKind::Lazy(fac), public, comments: vec![] }]
}
pub fn prefix(path: &str, items: impl IntoIterator<Item = Vec<GenMember>>) -> Vec<GenMember> {
@@ -167,10 +157,10 @@ pub struct GenMember {
pub comments: Vec<String>,
}
impl GenMember {
pub async fn into_api(self, tia_cx: &mut impl TreeIntoApiCtx) -> api::Member {
let name = i().i::<String>(&self.name).await;
pub(crate) async fn into_api(self, tia_cx: &mut impl TreeIntoApiCtx) -> api::Member {
let name = is(&self.name).await;
let kind = self.kind.into_api(&mut tia_cx.push_path(name.clone())).await;
let comments = join_all(self.comments.iter().map(async |cmt| i().i(cmt).await.to_api())).await;
let comments = join_all(self.comments.iter().map(async |cmt| is(cmt).await.to_api())).await;
api::Member { kind, name: name.to_api(), comments, exported: self.public }
}
}
@@ -181,9 +171,9 @@ pub enum MemKind {
Lazy(LazyMemberFactory),
}
impl MemKind {
pub async fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::MemberKind {
pub(crate) async fn into_api(self, ctx: &mut impl TreeIntoApiCtx) -> api::MemberKind {
match self {
Self::Lazy(lazy) => api::MemberKind::Lazy(ctx.with_lazy(lazy)),
Self::Lazy(lazy) => api::MemberKind::Lazy(add_lazy(ctx, lazy)),
Self::Const(c) => api::MemberKind::Const(c.serialize().await),
Self::Mod { members } => api::MemberKind::Module(api::Module {
members: stream(async |mut cx| {
@@ -199,29 +189,58 @@ impl MemKind {
}
}
pub trait TreeIntoApiCtx {
fn with_lazy(&mut self, fac: LazyMemberFactory) -> api::TreeId;
fn push_path(&mut self, seg: Tok<String>) -> impl TreeIntoApiCtx;
pub enum MemberRecord {
Gen(Vec<IStr>, LazyMemberFactory),
Res,
}
pub struct TreeIntoApiCtxImpl<'a, 'b> {
pub basepath: &'a [Tok<String>],
pub path: Substack<'a, Tok<String>>,
pub lazy_members: &'b mut HashMap<api::TreeId, MemberRecord>,
#[derive(Clone, Default)]
pub(crate) struct LazyMemberStore(Rc<RefCell<HashMap<api::TreeId, MemberRecord>>>);
task_local! {
static LAZY_MEMBERS: LazyMemberStore;
}
impl TreeIntoApiCtx for TreeIntoApiCtxImpl<'_, '_> {
fn push_path(&mut self, seg: Tok<String>) -> impl TreeIntoApiCtx {
TreeIntoApiCtxImpl {
lazy_members: self.lazy_members,
basepath: self.basepath,
path: self.path.push(seg),
}
}
fn with_lazy(&mut self, fac: LazyMemberFactory) -> api::TreeId {
let id = api::TreeId(NonZero::new((self.lazy_members.len() + 2) as u64).unwrap());
let path = self.basepath.iter().cloned().chain(self.path.unreverse()).collect_vec();
self.lazy_members.insert(id, MemberRecord::Gen(path, fac));
pub fn with_lazy_member_store<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
Box::pin(LAZY_MEMBERS.scope(LazyMemberStore::default(), fut))
}
fn add_lazy(cx: &impl TreeIntoApiCtx, fac: LazyMemberFactory) -> api::TreeId {
LAZY_MEMBERS.with(|lazy_members| {
let mut g = lazy_members.0.borrow_mut();
let id = api::TreeId(NonZero::new((g.len() + 2) as u64).unwrap());
let path = cx.path().collect_vec();
g.insert(id, MemberRecord::Gen(path, fac));
id
})
}
pub async fn get_lazy(id: api::TreeId) -> (Sym, MemKind) {
let (path, cb) =
LAZY_MEMBERS.with(|tbl| match tbl.0.borrow_mut().insert(id, MemberRecord::Res) {
None => panic!("Tree for ID not found"),
Some(MemberRecord::Res) => panic!("This tree has already been transmitted"),
Some(MemberRecord::Gen(path, cb)) => (path, cb),
});
let path = Sym::new(path).await.unwrap();
(path.clone(), cb.build(path).await)
}
pub(crate) trait TreeIntoApiCtx {
fn push_path(&mut self, seg: IStr) -> impl TreeIntoApiCtx;
fn path(&self) -> impl Iterator<Item = IStr>;
}
pub struct TreeIntoApiCtxImpl<'a> {
pub basepath: &'a [IStr],
pub path: Substack<'a, IStr>,
}
impl TreeIntoApiCtx for TreeIntoApiCtxImpl<'_> {
fn push_path(&mut self, seg: IStr) -> impl TreeIntoApiCtx {
TreeIntoApiCtxImpl { basepath: self.basepath, path: self.path.push(seg) }
}
fn path(&self) -> impl Iterator<Item = IStr> {
self.basepath.iter().cloned().chain(self.path.unreverse())
}
}

View File

@@ -5,7 +5,7 @@ use async_once_cell::OnceCell;
use derive_destructure::destructure;
use orchid_base::format::{FmtCtx, FmtUnit, Format, take_first_fmt};
use orchid_base::location::Pos;
use orchid_base::reqnot::Requester;
use orchid_base::reqnot::ClientExt;
use orchid_base::tree::AtomRepr;
use crate::api;
@@ -59,11 +59,11 @@ impl AtomHand {
pub async fn call(self, arg: Expr) -> Expr {
let owner_sys = self.0.owner.clone();
let ctx = owner_sys.ctx();
let reqnot = owner_sys.reqnot();
let client = owner_sys.client();
ctx.exprs.give_expr(arg.clone());
let ret = match Rc::try_unwrap(self.0) {
Ok(data) => reqnot.request(api::FinalCall(data.api(), arg.id())).await,
Err(hand) => reqnot.request(api::CallRef(hand.api_ref(), arg.id())).await,
Ok(data) => client.request(api::FinalCall(data.api(), arg.id())).await.unwrap(),
Err(hand) => client.request(api::CallRef(hand.api_ref(), arg.id())).await.unwrap(),
};
let val = Expr::from_api(&ret, PathSetBuilder::new(), ctx.clone()).await;
ctx.exprs.take_expr(arg.id());
@@ -74,19 +74,21 @@ impl AtomHand {
#[must_use]
pub fn ext(&self) -> &Extension { self.sys().ext() }
pub async fn req(&self, key: api::TStrv, req: Vec<u8>) -> Option<Vec<u8>> {
self.0.owner.reqnot().request(api::Fwded(self.0.api_ref(), key, req)).await
self.0.owner.client().request(api::Fwded(self.0.api_ref(), key, req)).await.unwrap()
}
#[must_use]
pub fn api_ref(&self) -> api::Atom { self.0.api_ref() }
#[must_use]
pub async fn to_string(&self) -> String { take_first_fmt(self, &self.0.owner.ctx().i).await }
pub async fn to_string(&self) -> String { take_first_fmt(self).await }
#[must_use]
pub fn downgrade(&self) -> WeakAtomHand { WeakAtomHand(Rc::downgrade(&self.0)) }
}
impl Format for AtomHand {
async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
(self.0.display.get_or_init(async {
FmtUnit::from_api(&self.0.owner.reqnot().request(api::AtomPrint(self.0.api_ref())).await)
FmtUnit::from_api(
&self.0.owner.client().request(api::AtomPrint(self.0.api_ref())).await.unwrap(),
)
}))
.await
.clone()

View File

@@ -3,19 +3,28 @@ use std::num::{NonZero, NonZeroU16};
use std::rc::{Rc, Weak};
use std::{fmt, ops};
use futures::future::LocalBoxFuture;
use futures_locks::RwLock;
use hashbrown::HashMap;
use orchid_base::builtin::Spawner;
use orchid_base::interner::Interner;
use orchid_base::logging::Logger;
use crate::api;
use crate::expr_store::ExprStore;
use crate::system::{System, WeakSystem};
use crate::tree::WeakRoot;
pub trait JoinHandle {
fn abort(&self);
fn join(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
}
pub trait Spawner {
fn spawn_obj(&self, fut: LocalBoxFuture<'static, ()>) -> Box<dyn JoinHandle>;
}
pub struct CtxData {
pub i: Interner,
pub spawn: Spawner,
spawner: Rc<dyn Spawner>,
pub msg_logs: Logger,
pub systems: RwLock<HashMap<api::SysId, WeakSystem>>,
pub system_id: RefCell<NonZeroU16>,
pub exprs: ExprStore,
@@ -37,16 +46,25 @@ impl WeakCtx {
}
impl Ctx {
#[must_use]
pub fn new(spawn: Spawner) -> Self {
pub fn new(msg_logs: Logger, spawner: impl Spawner + 'static) -> Self {
Self(Rc::new(CtxData {
spawn,
i: Interner::default(),
msg_logs,
spawner: Rc::new(spawner),
systems: RwLock::default(),
system_id: RefCell::new(NonZero::new(1).unwrap()),
exprs: ExprStore::default(),
root: RwLock::default(),
}))
}
/// Spawn a parallel future that you can join at any later time.
///
/// Don't use this for async Drop, use [orchid_base::stash::stash] instead.
/// If you use this for an actor object, make sure to actually join the
/// handle.
#[must_use]
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Box<dyn JoinHandle> {
self.spawner.spawn_obj(Box::pin(fut))
}
#[must_use]
pub(crate) async fn system_inst(&self, id: api::SysId) -> Option<System> {
self.systems.read().await.get(&id).and_then(WeakSystem::upgrade)
@@ -62,9 +80,6 @@ impl Ctx {
}
impl fmt::Debug for Ctx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ctx")
.field("i", &self.i)
.field("system_id", &self.system_id)
.finish_non_exhaustive()
f.debug_struct("Ctx").field("system_id", &self.system_id).finish_non_exhaustive()
}
}

View File

@@ -1,7 +1,7 @@
use hashbrown::HashSet;
use itertools::Itertools;
use orchid_base::error::{OrcErrv, OrcRes, Reporter, mk_errv};
use orchid_base::interner::{Interner, Tok};
use orchid_base::error::{OrcErrv, OrcRes, mk_errv};
use orchid_base::interner::{IStr, is};
use orchid_base::location::Pos;
use orchid_base::name::VName;
@@ -16,17 +16,17 @@ pub enum AbsPathError {
RootPath,
}
impl AbsPathError {
pub async fn err_obj(self, i: &Interner, pos: Pos, path: &str) -> OrcErrv {
pub async fn err_obj(self, pos: Pos, path: &str) -> OrcErrv {
let (descr, msg) = match self {
AbsPathError::RootPath => (
i.i("Path ends on root module").await,
is("Path ends on root module").await,
format!(
"{path} is equal to the empty path. You cannot directly reference the root. \
Use one fewer 'super::' or add more segments to make it valid."
),
),
AbsPathError::TooManySupers => (
i.i("Too many 'super::' steps in path").await,
is("Too many 'super::' steps in path").await,
format!("{path} is leading outside the root."),
),
};
@@ -41,13 +41,9 @@ impl AbsPathError {
///
/// if the relative path contains as many or more `super` segments than the
/// length of the absolute path.
pub async fn absolute_path(
mut cwd: &[Tok<String>],
mut rel: &[Tok<String>],
i: &Interner,
) -> Result<VName, AbsPathError> {
let i_self = i.i("self").await;
let i_super = i.i("super").await;
pub async fn absolute_path(mut cwd: &[IStr], mut rel: &[IStr]) -> Result<VName, AbsPathError> {
let i_self = is("self").await;
let i_super = is("super").await;
let mut relative = false;
if let Some((_, tail)) = rel.split_first().filter(|(h, _)| **h == i_self) {
rel = tail;
@@ -63,19 +59,13 @@ pub async fn absolute_path(
.map_err(|_| AbsPathError::RootPath)
}
pub struct DealiasCtx<'a> {
pub i: &'a Interner,
pub rep: &'a Reporter,
}
pub async fn resolv_glob<Mod: Tree>(
cwd: &[Tok<String>],
cwd: &[IStr],
root: &Mod,
abs_path: &[Tok<String>],
abs_path: &[IStr],
pos: Pos,
i: &Interner,
ctx: &mut Mod::Ctx<'_>,
) -> OrcRes<HashSet<Tok<String>>> {
) -> OrcRes<HashSet<IStr>> {
let coprefix_len = cwd.iter().zip(abs_path).take_while(|(a, b)| a == b).count();
let (co_prefix, diff_path) = abs_path.split_at(abs_path.len().min(coprefix_len + 1));
let fst_diff =
@@ -89,7 +79,7 @@ pub async fn resolv_glob<Mod: Tree>(
ChildErrorKind::Missing => ("Invalid import path", format!("{path} not found")),
ChildErrorKind::Private => ("Import inaccessible", format!("{path} is private")),
};
return Err(mk_errv(i.i(tk).await, msg, [pos]));
return Err(mk_errv(is(tk).await, msg, [pos]));
},
};
Ok(target_module.children(coprefix_len < abs_path.len()))
@@ -100,11 +90,11 @@ pub type ChildResult<'a, T> = Result<&'a T, ChildErrorKind>;
pub trait Tree {
type Ctx<'a>;
#[must_use]
fn children(&self, public_only: bool) -> HashSet<Tok<String>>;
fn children(&self, public_only: bool) -> HashSet<IStr>;
#[must_use]
fn child(
&self,
key: Tok<String>,
key: IStr,
public_only: bool,
ctx: &mut Self::Ctx<'_>,
) -> impl Future<Output = ChildResult<'_, Self>>;
@@ -135,7 +125,7 @@ pub struct ChildError {
pub async fn walk<'a, T: Tree>(
root: &'a T,
public_only: bool,
path: impl IntoIterator<Item = Tok<String>>,
path: impl IntoIterator<Item = IStr>,
ctx: &mut T::Ctx<'_>,
) -> Result<&'a T, ChildError> {
let mut cur = root;

View File

@@ -4,11 +4,10 @@ use bound::Bound;
use futures::FutureExt;
use futures_locks::{RwLockWriteGuard, TryLockError};
use orchid_base::error::OrcErrv;
use orchid_base::format::{FmtCtxImpl, Format, take_first};
use orchid_base::format::fmt;
use orchid_base::location::Pos;
use orchid_base::logging::Logger;
use orchid_base::logging::logger;
use crate::ctx::Ctx;
use crate::expr::{Expr, ExprKind, PathSet, Step};
use crate::tree::Root;
@@ -30,21 +29,19 @@ pub enum ExecResult {
}
pub struct ExecCtx {
ctx: Ctx,
gas: Option<u64>,
stack: Vec<ExprGuard>,
cur: ExprGuard,
cur_pos: Pos,
did_pop: bool,
logger: Logger,
root: Root,
}
impl ExecCtx {
#[must_use]
pub async fn new(ctx: Ctx, logger: Logger, root: Root, init: Expr) -> Self {
pub async fn new(root: Root, init: Expr) -> Self {
let cur_pos = init.pos();
let cur = Bound::async_new(init, |init| init.kind().write()).await;
Self { ctx, gas: None, stack: vec![], cur, cur_pos, did_pop: false, logger, root }
Self { gas: None, stack: vec![], cur, cur_pos, did_pop: false, root }
}
#[must_use]
pub fn remaining_gas(&self) -> u64 { self.gas.expect("queried remaining_gas but no gas was set") }
@@ -89,8 +86,7 @@ impl ExecCtx {
while self.use_gas(1) {
let mut kind_swap = ExprKind::Missing;
mem::swap(&mut kind_swap, &mut self.cur);
let unit = kind_swap.print(&FmtCtxImpl { i: &self.ctx.i }).await;
writeln!(self.logger, "Exxecute lvl{} {}", self.stack.len(), take_first(&unit, true));
writeln!(logger(), "Exxecute lvl{} {}", self.stack.len(), fmt(&kind_swap).await);
let (kind, op) = match kind_swap {
ExprKind::Identity(target) => {
let inner = self.unpack_ident(&target).await;

View File

@@ -9,7 +9,6 @@ use futures_locks::RwLock;
use itertools::Itertools;
use orchid_base::error::OrcErrv;
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
use orchid_base::interner::Interner;
use orchid_base::location::{Pos, SrcRange};
use orchid_base::name::Sym;
use orchid_base::tl_cache;
@@ -56,13 +55,13 @@ impl Expr {
}
#[must_use]
pub async fn from_api(api: &api::Expression, psb: PathSetBuilder<'_, u64>, ctx: Ctx) -> Self {
let pos = Pos::from_api(&api.location, &ctx.i).await;
let pos = Pos::from_api(&api.location).await;
let kind = match &api.kind {
api::ExpressionKind::Arg(n) => {
assert!(psb.register_arg(n), "Arguments must be enclosed in a matching lambda");
ExprKind::Arg
},
api::ExpressionKind::Bottom(bot) => ExprKind::Bottom(OrcErrv::from_api(bot, &ctx.i).await),
api::ExpressionKind::Bottom(bot) => ExprKind::Bottom(OrcErrv::from_api(bot).await),
api::ExpressionKind::Call(f, x) => {
let (lpsb, rpsb) = psb.split();
ExprKind::Call(
@@ -70,7 +69,7 @@ impl Expr {
Expr::from_api(x, rpsb, ctx).boxed_local().await,
)
},
api::ExpressionKind::Const(name) => ExprKind::Const(Sym::from_api(*name, &ctx.i).await),
api::ExpressionKind::Const(name) => ExprKind::Const(Sym::from_api(*name).await),
api::ExpressionKind::Lambda(x, body) => {
let lbuilder = psb.lambda(x);
let body = Expr::from_api(body, lbuilder.stack(), ctx).boxed_local().await;
@@ -326,12 +325,7 @@ impl WeakExpr {
impl TokenVariant<api::ExprTicket> for Expr {
type FromApiCtx<'a> = ExprStore;
async fn from_api(
api: &api::ExprTicket,
ctx: &mut Self::FromApiCtx<'_>,
_: SrcRange,
_: &Interner,
) -> Self {
async fn from_api(api: &api::ExprTicket, ctx: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
ctx.get_expr(*api).expect("Invalid ticket")
}
type ToApiCtx<'a> = ExprStore;
@@ -348,12 +342,7 @@ pub struct ExprWillPanic;
impl TokenVariant<api::Expression> for Expr {
type FromApiCtx<'a> = Ctx;
async fn from_api(
api: &api::Expression,
ctx: &mut Self::FromApiCtx<'_>,
_: SrcRange,
_: &Interner,
) -> Self {
async fn from_api(api: &api::Expression, ctx: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
Self::from_api(api, PathSetBuilder::new(), ctx.clone()).await
}
type ToApiCtx<'a> = ExprWillPanic;

View File

@@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::future::Future;
use std::io;
use std::num::NonZeroU64;
use std::pin::pin;
use std::pin::Pin;
use std::rc::{Rc, Weak};
use async_fn_stream::stream;
@@ -10,28 +10,33 @@ use derive_destructure::destructure;
use futures::channel::mpsc::{Sender, channel};
use futures::future::{join, join_all};
use futures::lock::Mutex;
use futures::{SinkExt, StreamExt, stream};
use hashbrown::HashMap;
use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, SinkExt, StreamExt};
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use orchid_api_traits::Request;
use orchid_base::builtin::ExtInit;
use orchid_api_traits::{Decode, Encode, Request};
use orchid_base::clone;
use orchid_base::format::{FmtCtxImpl, Format};
use orchid_base::interner::Tok;
use orchid_base::interner::{IStr, IStrv, es, ev, is, iv};
use orchid_base::location::Pos;
use orchid_base::logging::Logger;
use orchid_base::logging::logger;
use orchid_base::name::Sym;
use orchid_base::reqnot::{DynRequester, ReqNot, Requester as _};
use orchid_base::reqnot::{Client, ClientExt, MsgReaderExt, ReqHandleExt, ReqReaderExt, io_comm};
use orchid_base::stash::{stash, with_stash};
use orchid_base::tree::AtomRepr;
use crate::api;
use crate::atom::AtomHand;
use crate::ctx::Ctx;
use crate::ctx::{Ctx, JoinHandle};
use crate::dealias::{ChildError, ChildErrorKind, walk};
use crate::expr::{Expr, PathSetBuilder};
use crate::system::SystemCtor;
use crate::tree::MemberKind;
pub struct ExtPort {
pub input: Pin<Box<dyn AsyncWrite>>,
pub output: Pin<Box<dyn AsyncRead>>,
}
pub struct ReqPair<R: Request>(R, Sender<R::Response>);
/// Data held about an Extension. This is refcounted within [Extension]. It's
@@ -42,69 +47,47 @@ pub struct ReqPair<R: Request>(R, Sender<R::Response>);
pub struct ExtensionData {
name: String,
ctx: Ctx,
reqnot: ReqNot<api::HostMsgSet>,
join_ext: Option<Box<dyn JoinHandle>>,
client: Rc<dyn Client>,
systems: Vec<SystemCtor>,
logger: Logger,
next_pars: RefCell<NonZeroU64>,
exiting_snd: Sender<()>,
lex_recur: Mutex<HashMap<api::ParsId, Sender<ReqPair<api::SubLex>>>>,
strings: RefCell<HashSet<IStr>>,
string_vecs: RefCell<HashSet<IStrv>>,
}
impl Drop for ExtensionData {
fn drop(&mut self) {
let reqnot = self.reqnot.clone();
let mut exiting_snd = self.exiting_snd.clone();
(self.ctx.spawn)(Box::pin(async move {
reqnot.notify(api::HostExtNotif::Exit).await;
exiting_snd.send(()).await.unwrap()
}))
let client = self.client.clone();
let join_ext = self.join_ext.take().expect("Only called once in Drop");
stash(async move {
client.notify(api::HostExtNotif::Exit).await.unwrap();
join_ext.join().await;
})
}
}
#[derive(Clone)]
pub struct Extension(Rc<ExtensionData>);
impl Extension {
pub fn new(init: ExtInit, logger: Logger, msg_logger: Logger, ctx: Ctx) -> io::Result<Self> {
pub async fn new(mut init: ExtPort, ctx: Ctx) -> io::Result<Self> {
api::HostHeader { log_strategy: logger().strat(), msg_logs: ctx.msg_logs.strat() }
.encode(init.input.as_mut())
.await
.unwrap();
init.input.flush().await.unwrap();
let header = api::ExtensionHeader::decode(init.output.as_mut()).await.unwrap();
Ok(Self(Rc::new_cyclic(|weak: &Weak<ExtensionData>| {
let init = Rc::new(init);
let (exiting_snd, exiting_rcv) = channel::<()>(0);
(ctx.spawn)({
clone!(init, weak, ctx);
Box::pin(async move {
let rcv_stream = stream(async |mut cx| {
loop {
cx.emit(init.recv().await).await
}
});
let mut event_stream = pin!(stream::select(exiting_rcv.map(|()| None), rcv_stream));
while let Some(Some(msg)) = event_stream.next().await {
if let Some(reqnot) = weak.upgrade().map(|rc| rc.reqnot.clone()) {
let reqnot = reqnot.clone();
(ctx.spawn)(Box::pin(async move {
reqnot.receive(&msg).await;
}))
}
}
})
});
ExtensionData {
name: init.name.clone(),
exiting_snd,
ctx: ctx.clone(),
systems: (init.systems.iter().cloned())
.map(|decl| SystemCtor { decl, ext: WeakExtension(weak.clone()) })
.collect(),
logger: logger.clone(),
next_pars: RefCell::new(NonZeroU64::new(1).unwrap()),
lex_recur: Mutex::default(),
reqnot: ReqNot::new(
msg_logger,
move |sfn, _| clone!(init; Box::pin(async move { init.send(sfn).await })),
clone!(weak; move |notif, _| {
clone!(weak; Box::pin(async move {
// context not needed because exit is extension-initiated
let (client, _, future) = io_comm(
Rc::new(Mutex::new(init.input)),
Mutex::new(init.output),
clone!(weak; async move |reader| {
with_stash(async {
let this = Extension(weak.upgrade().unwrap());
let notif = reader.read::<api::ExtHostNotif>().await.unwrap();
if !matches!(notif, api::ExtHostNotif::Log(_)) {
writeln!(this.reqnot().logger(), "Host received notif {notif:?}");
writeln!(logger(), "Host received notif {notif:?}");
}
match notif {
api::ExtHostNotif::ExprNotif(api::ExprNotif::Acquire(acq)) => {
@@ -115,153 +98,191 @@ impl Extension {
if this.is_own_sys(rel.0).await {
this.0.ctx.exprs.take_expr(rel.1);
} else {
writeln!(this.reqnot().logger(), "Not our system {:?}", rel.0)
writeln!(this.0.ctx.msg_logs, "Not our system {:?}", rel.0)
}
},
api::ExtHostNotif::Log(api::Log(str)) => this.logger().log(str),
}
}))}),
{
clone!(weak, ctx);
move |hand, req| {
clone!(weak, ctx);
Box::pin(async move {
let this = Self(weak.upgrade().unwrap());
if !matches!(req, api::ExtHostReq::ExtAtomPrint(_)) {
writeln!(this.reqnot().logger(), "Host received request {req:?}");
api::ExtHostNotif::Log(api::Log(str)) => logger().log(str),
api::ExtHostNotif::Sweeped(data) => {
for i in join_all(data.strings.into_iter().map(es)).await {
this.0.strings.borrow_mut().remove(&i);
}
let i = this.ctx().i.clone();
match req {
api::ExtHostReq::Ping(ping) => hand.handle(&ping, &()).await,
api::ExtHostReq::IntReq(intreq) => match intreq {
api::IntReq::InternStr(s) => hand.handle(&s, &i.i(&*s.0).await.to_api()).await,
api::IntReq::InternStrv(v) => {
let tokens = join_all(v.0.iter().map(|m| i.ex(*m))).await;
hand.handle(&v, &i.i(&tokens).await.to_api()).await
},
api::IntReq::ExternStr(si) =>
hand.handle(&si, &Tok::<String>::from_api(si.0, &i).await.rc()).await,
api::IntReq::ExternStrv(vi) => {
let markerv = (i.ex(vi.0).await.iter()).map(|t| t.to_api()).collect_vec();
hand.handle(&vi, &markerv).await
},
for i in join_all(data.vecs.into_iter().map(ev)).await {
this.0.string_vecs.borrow_mut().remove(&i);
}
},
}
Ok(())
}).await
}),
{
clone!(weak, ctx);
async move |mut reader| {
with_stash(async {
let this = Self(weak.upgrade().unwrap());
let req = reader.read_req::<api::ExtHostReq>().await.unwrap();
let handle = reader.finish().await;
if !matches!(req, api::ExtHostReq::ExtAtomPrint(_)) {
writeln!(logger(), "Host received request {req:?}");
}
match req {
api::ExtHostReq::Ping(ping) => handle.reply(&ping, &()).await,
api::ExtHostReq::IntReq(intreq) => match intreq {
api::IntReq::InternStr(s) => {
let i = is(&s.0).await;
this.0.strings.borrow_mut().insert(i.clone());
handle.reply(&s, &i.to_api()).await
},
api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => {
let sys =
ctx.system_inst(atom.owner).await.expect("owner of live atom dropped");
let reply =
sys.reqnot().request(api::Fwded(fw.0.clone(), *key, body.clone())).await;
hand.handle(fw, &reply).await
api::IntReq::InternStrv(v) => {
let tokens = join_all(v.0.iter().map(|m| es(*m))).await;
this.0.strings.borrow_mut().extend(tokens.iter().cloned());
let i = iv(&tokens).await;
this.0.string_vecs.borrow_mut().insert(i.clone());
handle.reply(&v, &i.to_api()).await
},
api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => {
let sys = ctx.system_inst(id).await.unwrap();
hand.handle(fw, &sys.request(body.clone()).await).await
api::IntReq::ExternStr(si) => {
let i = es(si.0).await;
this.0.strings.borrow_mut().insert(i.clone());
handle.reply(&si, &i.to_string()).await
},
api::ExtHostReq::SubLex(sl) => {
let (rep_in, mut rep_out) = channel(0);
{
let lex_g = this.0.lex_recur.lock().await;
let mut req_in =
lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid");
req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap();
api::IntReq::ExternStrv(vi) => {
let i = ev(vi.0).await;
this.0.strings.borrow_mut().extend(i.iter().cloned());
this.0.string_vecs.borrow_mut().insert(i.clone());
let markerv = i.iter().map(|t| t.to_api()).collect_vec();
handle.reply(&vi, &markerv).await
},
},
api::ExtHostReq::Fwd(ref fw @ api::Fwd(ref atom, ref key, ref body)) => {
let sys = ctx.system_inst(atom.owner).await.expect("owner of live atom dropped");
let client = sys.client();
let reply =
client.request(api::Fwded(fw.0.clone(), *key, body.clone())).await.unwrap();
handle.reply(fw, &reply).await
},
api::ExtHostReq::SysFwd(ref fw @ api::SysFwd(id, ref body)) => {
let sys = ctx.system_inst(id).await.unwrap();
handle.reply(fw, &sys.request(body.clone()).await).await
},
api::ExtHostReq::SubLex(sl) => {
let (rep_in, mut rep_out) = channel(0);
{
let lex_g = this.0.lex_recur.lock().await;
let mut req_in =
lex_g.get(&sl.id).cloned().expect("Sublex for nonexistent lexid");
req_in.send(ReqPair(sl.clone(), rep_in)).await.unwrap();
}
handle.reply(&sl, &rep_out.next().await.unwrap()).await
},
api::ExtHostReq::ExprReq(expr_req) => match expr_req {
api::ExprReq::Inspect(ins @ api::Inspect { target }) => {
let expr = ctx.exprs.get_expr(target).expect("Invalid ticket");
handle
.reply(&ins, &api::Inspected {
refcount: expr.strong_count() as u32,
location: expr.pos().to_api(),
kind: expr.to_api().await,
})
.await
},
api::ExprReq::Create(ref cre @ api::Create(ref expr)) => {
let expr = Expr::from_api(expr, PathSetBuilder::new(), ctx.clone()).await;
let expr_id = expr.id();
ctx.exprs.give_expr(expr);
handle.reply(cre, &expr_id).await
},
},
api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => {
let reply: <api::LsModule as Request>::Response = 'reply: {
let path = ev(path).await;
let root = (ctx.root.read().await.upgrade())
.expect("LSModule called when root isn't in context");
let root_data = &*root.0.read().await;
let mut walk_ctx = (ctx.clone(), &root_data.consts);
let module =
match walk(&root_data.root, false, path.iter().cloned(), &mut walk_ctx).await
{
Ok(module) => module,
Err(ChildError { kind, .. }) =>
break 'reply Err(match kind {
ChildErrorKind::Private => panic!("Access checking was disabled"),
ChildErrorKind::Constant => api::LsModuleError::IsConstant,
ChildErrorKind::Missing => api::LsModuleError::InvalidPath,
}),
};
let mut members = std::collections::HashMap::new();
for (k, v) in &module.members {
let kind = match v.kind(ctx.clone(), &root_data.consts).await {
MemberKind::Const => api::MemberInfoKind::Constant,
MemberKind::Module(_) => api::MemberInfoKind::Module,
};
members.insert(k.to_api(), api::MemberInfo { public: v.public, kind });
}
hand.handle(&sl, &rep_out.next().await.unwrap()).await
},
api::ExtHostReq::ExprReq(expr_req) => match expr_req {
api::ExprReq::Inspect(ins @ api::Inspect { target }) => {
let expr = ctx.exprs.get_expr(target).expect("Invalid ticket");
hand
.handle(&ins, &api::Inspected {
refcount: expr.strong_count() as u32,
location: expr.pos().to_api(),
kind: expr.to_api().await,
})
.await
},
api::ExprReq::Create(ref cre @ api::Create(ref expr)) => {
let expr = Expr::from_api(expr, PathSetBuilder::new(), ctx.clone()).await;
let expr_id = expr.id();
ctx.exprs.give_expr(expr);
hand.handle(cre, &expr_id).await
},
},
api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => {
let reply: <api::LsModule as Request>::Response = 'reply: {
let path = i.ex(path).await;
let root = (ctx.root.read().await.upgrade())
.expect("LSModule called when root isn't in context");
let root_data = &*root.0.read().await;
let mut walk_ctx = (ctx.clone(), &root_data.consts);
let module =
match walk(&root_data.root, false, path.iter().cloned(), &mut walk_ctx)
.await
{
Ok(module) => module,
Err(ChildError { kind, .. }) =>
break 'reply Err(match kind {
ChildErrorKind::Private => panic!("Access checking was disabled"),
ChildErrorKind::Constant => api::LsModuleError::IsConstant,
ChildErrorKind::Missing => api::LsModuleError::InvalidPath,
}),
};
let mut members = std::collections::HashMap::new();
for (k, v) in &module.members {
let kind = match v.kind(ctx.clone(), &root_data.consts).await {
MemberKind::Const => api::MemberInfoKind::Constant,
MemberKind::Module(_) => api::MemberInfoKind::Module,
};
members.insert(k.to_api(), api::MemberInfo { public: v.public, kind });
}
Ok(api::ModuleInfo { members })
};
hand.handle(ls, &reply).await
},
api::ExtHostReq::ResolveNames(ref rn) => {
let api::ResolveNames { constid, names, sys } = rn;
let mut resolver = {
let systems = ctx.systems.read().await;
let weak_sys = systems.get(sys).expect("ResolveNames for invalid sys");
let sys = weak_sys.upgrade().expect("ResolveNames after sys drop");
sys.name_resolver(*constid).await
};
let responses = stream(async |mut cx| {
for name in names {
cx.emit(match resolver(&ctx.i.ex(*name).await[..]).await {
Ok(abs) => Ok(abs.to_sym(&ctx.i).await.to_api()),
Err(e) => Err(e.to_api()),
})
.await
}
})
.collect()
.await;
hand.handle(rn, &responses).await
},
api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => {
let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await;
let unit = atom.print(&FmtCtxImpl { i: &this.ctx().i }).await;
hand.handle(eap, &unit.to_api()).await
},
}
})
}
},
),
Ok(api::ModuleInfo { members })
};
handle.reply(ls, &reply).await
},
api::ExtHostReq::ResolveNames(ref rn) => {
let api::ResolveNames { constid, names, sys } = rn;
let mut resolver = {
let systems = ctx.systems.read().await;
let weak_sys = systems.get(sys).expect("ResolveNames for invalid sys");
let sys = weak_sys.upgrade().expect("ResolveNames after sys drop");
sys.name_resolver(*constid).await
};
let responses = stream(async |mut cx| {
for name in names {
cx.emit(match resolver(&ev(*name).await[..]).await {
Ok(abs) => Ok(abs.to_sym().await.to_api()),
Err(e) => Err(e.to_api()),
})
.await
}
})
.collect()
.await;
handle.reply(rn, &responses).await
},
api::ExtHostReq::ExtAtomPrint(ref eap @ api::ExtAtomPrint(ref atom)) => {
let atom = AtomHand::from_api(atom, Pos::None, &mut ctx.clone()).await;
let unit = atom.print(&FmtCtxImpl::default()).await;
handle.reply(eap, &unit.to_api()).await
},
}
})
.await
}
},
);
let join_ext = ctx.spawn(async {
future.await.unwrap();
// extension exited successfully
});
ExtensionData {
name: header.name.clone(),
ctx: ctx.clone(),
systems: (header.systems.iter().cloned())
.map(|decl| SystemCtor { decl, ext: WeakExtension(weak.clone()) })
.collect(),
join_ext: Some(join_ext),
next_pars: RefCell::new(NonZeroU64::new(1).unwrap()),
lex_recur: Mutex::default(),
client: Rc::new(client),
strings: RefCell::default(),
string_vecs: RefCell::default(),
}
})))
}
pub fn name(&self) -> &String { &self.0.name }
#[must_use]
pub fn reqnot(&self) -> &ReqNot<api::HostMsgSet> { &self.0.reqnot }
pub fn client(&self) -> &dyn Client { &*self.0.client }
#[must_use]
pub fn ctx(&self) -> &Ctx { &self.0.ctx }
#[must_use]
pub fn logger(&self) -> &Logger { &self.0.logger }
pub fn system_ctors(&self) -> impl Iterator<Item = &SystemCtor> { self.0.systems.iter() }
#[must_use]
pub async fn is_own_sys(&self, id: api::SysId) -> bool {
let Some(sys) = self.ctx().system_inst(id).await else {
writeln!(self.logger(), "Invalid system ID {id:?}");
writeln!(logger(), "Invalid system ID {id:?}");
return false;
};
Rc::ptr_eq(&self.0, &sys.ext().0)
@@ -274,7 +295,7 @@ impl Extension {
}
pub(crate) async fn lex_req<F: Future<Output = Option<api::SubLexed>>>(
&self,
source: Tok<String>,
source: IStr,
src: Sym,
pos: u32,
sys: api::SysId,
@@ -287,9 +308,10 @@ impl Extension {
self.0.lex_recur.lock().await.insert(id, req_in); // lex_recur released
let (ret, ()) = join(
async {
let res = (self.reqnot())
let res = (self.client())
.request(api::LexExpr { id, pos, sys, src: src.to_api(), text: source.to_api() })
.await;
.await
.unwrap();
// collect sender to unblock recursion handler branch before returning
self.0.lex_recur.lock().await.remove(&id);
res
@@ -306,10 +328,10 @@ impl Extension {
}
pub fn system_drop(&self, id: api::SysId) {
let rc = self.clone();
(self.ctx().spawn)(Box::pin(async move {
rc.reqnot().request(api::SystemDrop(id)).await;
let _ = self.ctx().spawn(with_stash(async move {
rc.client().request(api::SystemDrop(id)).await.unwrap();
rc.ctx().systems.write().await.remove(&id);
}))
}));
}
#[must_use]
pub fn downgrade(&self) -> WeakExtension { WeakExtension(Rc::downgrade(&self.0)) }

View File

@@ -1,10 +1,8 @@
use std::rc::Rc;
use futures::FutureExt;
use futures::lock::Mutex;
use orchid_base::clone;
use orchid_base::error::{OrcErrv, OrcRes, mk_errv};
use orchid_base::interner::Tok;
use orchid_base::interner::{IStr, is};
use orchid_base::location::SrcRange;
use orchid_base::name::Sym;
use orchid_base::parse::{name_char, name_start, op_char, unrep_space};
@@ -20,7 +18,7 @@ use crate::system::System;
pub struct LexCtx<'a> {
pub systems: &'a [System],
pub source: &'a Tok<String>,
pub source: &'a IStr,
pub path: &'a Sym,
pub tail: &'a str,
pub sub_trees: &'a mut Vec<Expr>,
@@ -60,7 +58,7 @@ impl<'a> LexCtx<'a> {
}
#[must_use]
pub async fn des_subtree(&mut self, tree: &api::TokenTree, exprs: ExprStore) -> ParsTokTree {
ParsTokTree::from_api(tree, &mut { exprs }, &mut self.ctx.clone(), self.path, &self.ctx.i).await
ParsTokTree::from_api(tree, &mut { exprs }, &mut self.ctx.clone(), self.path).await
}
#[must_use]
pub fn strip_char(&mut self, tgt: char) -> bool {
@@ -98,21 +96,21 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes<ParsTokTree> {
let name = &ctx.tail[..ctx.tail.len() - tail.len() - "::".len()];
ctx.set_tail(tail);
let body = lex_once(ctx).boxed_local().await?;
ParsTok::NS(ctx.ctx.i.i(name).await, Box::new(body))
ParsTok::NS(is(name).await, Box::new(body))
} else if ctx.strip_prefix("--[") {
let Some((cmt, tail)) = ctx.tail.split_once("]--") else {
return Err(mk_errv(
ctx.ctx.i.i("Unterminated block comment").await,
is("Unterminated block comment").await,
"This block comment has no ending ]--",
[SrcRange::new(start..start + 3, ctx.path)],
));
};
ctx.set_tail(tail);
ParsTok::Comment(Rc::new(cmt.to_string()))
ParsTok::Comment(is(cmt).await)
} else if let Some(tail) = ctx.tail.strip_prefix("--").filter(|t| !t.starts_with(op_char)) {
let end = tail.find(['\n', '\r']).map_or(tail.len(), |n| n - 1);
ctx.push_pos(end as u32);
ParsTok::Comment(Rc::new(tail[2..end].to_string()))
ParsTok::Comment(is(&tail[2..end]).await)
} else if let Some(tail) = ctx.tail.strip_prefix('\\').filter(|t| t.starts_with(name_start)) {
// fanciness like \$placeh in templates is resolved in the macro engine.
ctx.set_tail(tail);
@@ -125,7 +123,7 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes<ParsTokTree> {
while !ctx.strip_char(*rp) {
if ctx.tail.is_empty() {
return Err(mk_errv(
ctx.ctx.i.i("unclosed paren").await,
is("unclosed paren").await,
format!("this {lp} has no matching {rp}"),
[SrcRange::new(start..start + 1, ctx.path)],
));
@@ -162,10 +160,7 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes<ParsTokTree> {
})
.await;
match lx {
Err(e) =>
return Err(
errors.into_iter().fold(OrcErrv::from_api(&e, &ctx.ctx.i).await, |a, b| a + b),
),
Err(e) => return Err(errors.into_iter().fold(OrcErrv::from_api(&e).await, |a, b| a + b)),
Ok(Some(lexed)) => {
ctx.set_pos(lexed.pos);
let lexed_tree = ctx.des_subtree(&lexed.expr, temp_store).await;
@@ -185,12 +180,12 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes<ParsTokTree> {
}
}
if ctx.tail.starts_with(name_start) {
ParsTok::Name(ctx.ctx.i.i(ctx.get_start_matches(name_char)).await)
ParsTok::Name(is(ctx.get_start_matches(name_char)).await)
} else if ctx.tail.starts_with(op_char) {
ParsTok::Name(ctx.ctx.i.i(ctx.get_start_matches(op_char)).await)
ParsTok::Name(is(ctx.get_start_matches(op_char)).await)
} else {
return Err(mk_errv(
ctx.ctx.i.i("Unrecognized character").await,
is("Unrecognized character").await,
"The following syntax is meaningless.",
[SrcRange::new(start..start + 1, ctx.path)],
));
@@ -199,12 +194,7 @@ pub async fn lex_once(ctx: &mut LexCtx<'_>) -> OrcRes<ParsTokTree> {
Ok(ParsTokTree { tok, sr: SrcRange::new(start..ctx.get_pos(), ctx.path) })
}
pub async fn lex(
text: Tok<String>,
path: Sym,
systems: &[System],
ctx: &Ctx,
) -> OrcRes<Vec<ParsTokTree>> {
pub async fn lex(text: IStr, path: Sym, systems: &[System], ctx: &Ctx) -> OrcRes<Vec<ParsTokTree>> {
let mut sub_trees = Vec::new();
let mut ctx =
LexCtx { source: &text, sub_trees: &mut sub_trees, tail: &text[..], systems, path: &path, ctx };

View File

@@ -1,12 +1,11 @@
use futures::FutureExt;
use itertools::Itertools;
use orchid_base::error::{OrcRes, Reporter, mk_errv};
use orchid_base::error::{OrcRes, mk_errv, report};
use orchid_base::format::fmt;
use orchid_base::interner::{Interner, Tok};
use orchid_base::interner::{IStr, is};
use orchid_base::name::Sym;
use orchid_base::parse::{
Comment, Import, ParseCtx, Parsed, Snippet, expect_end, line_items, parse_multiname,
try_pop_no_fluff,
Comment, Import, Parsed, Snippet, expect_end, line_items, parse_multiname, try_pop_no_fluff,
};
use orchid_base::tree::{Paren, TokTree, Token};
use substack::Substack;
@@ -22,12 +21,6 @@ pub struct HostParseCtxImpl<'a> {
pub ctx: Ctx,
pub src: Sym,
pub systems: &'a [System],
pub rep: &'a Reporter,
}
impl ParseCtx for HostParseCtxImpl<'_> {
fn rep(&self) -> &Reporter { self.rep }
fn i(&self) -> &Interner { &self.ctx.i }
}
impl HostParseCtx for HostParseCtxImpl<'_> {
@@ -36,7 +29,7 @@ impl HostParseCtx for HostParseCtxImpl<'_> {
fn src_path(&self) -> Sym { self.src.clone() }
}
pub trait HostParseCtx: ParseCtx {
pub trait HostParseCtx {
#[must_use]
fn ctx(&self) -> &Ctx;
#[must_use]
@@ -47,14 +40,14 @@ pub trait HostParseCtx: ParseCtx {
pub async fn parse_items(
ctx: &impl HostParseCtx,
path: Substack<'_, Tok<String>>,
path: Substack<'_, IStr>,
items: ParsSnippet<'_>,
) -> OrcRes<Vec<Item>> {
let lines = line_items(ctx, items).await;
let lines = line_items(items).await;
let mut line_ok = Vec::new();
for Parsed { output: comments, tail } in lines {
match parse_item(ctx, path.clone(), comments, tail).boxed_local().await {
Err(e) => ctx.rep().report(e),
Err(e) => report(e),
Ok(l) => line_ok.extend(l),
}
}
@@ -63,23 +56,23 @@ pub async fn parse_items(
pub async fn parse_item(
ctx: &impl HostParseCtx,
path: Substack<'_, Tok<String>>,
path: Substack<'_, IStr>,
comments: Vec<Comment>,
item: ParsSnippet<'_>,
) -> OrcRes<Vec<Item>> {
match item.pop_front() {
Some((TokTree { tok: Token::Name(n), .. }, postdisc)) => match n {
n if *n == ctx.i().i("export").await => match try_pop_no_fluff(ctx, postdisc).await? {
n if *n == is("export").await => match try_pop_no_fluff(postdisc).await? {
Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } =>
parse_exportable_item(ctx, path, comments, true, n.clone(), tail).await,
Parsed { output, tail: _ } => Err(mk_errv(
ctx.i().i("Malformed export").await,
is("Malformed export").await,
"`export` can either prefix other lines or list names inside ( )",
[output.sr()],
)),
},
n if *n == ctx.i().i("import").await => {
let imports = parse_import(ctx, postdisc).await?;
n if *n == is("import").await => {
let imports = parse_import(postdisc).await?;
Ok(Vec::from_iter(imports.into_iter().map(|t| Item {
comments: comments.clone(),
sr: t.sr.clone(),
@@ -88,33 +81,29 @@ pub async fn parse_item(
},
n => parse_exportable_item(ctx, path, comments, false, n.clone(), postdisc).await,
},
Some(_) => Err(mk_errv(
ctx.i().i("Expected a line type").await,
"All lines must begin with a keyword",
[item.sr()],
)),
Some(_) =>
Err(mk_errv(is("Expected a line type").await, "All lines must begin with a keyword", [
item.sr()
])),
None => unreachable!("These lines are filtered and aggregated in earlier stages"),
}
}
pub async fn parse_import<'a>(
ctx: &impl HostParseCtx,
tail: ParsSnippet<'a>,
) -> OrcRes<Vec<Import>> {
let Parsed { output: imports, tail } = parse_multiname(ctx, tail).await?;
expect_end(ctx, tail).await?;
pub async fn parse_import<'a>(tail: ParsSnippet<'a>) -> OrcRes<Vec<Import>> {
let Parsed { output: imports, tail } = parse_multiname(tail).await?;
expect_end(tail).await?;
Ok(imports)
}
pub async fn parse_exportable_item<'a>(
ctx: &impl HostParseCtx,
path: Substack<'_, Tok<String>>,
path: Substack<'_, IStr>,
comments: Vec<Comment>,
exported: bool,
discr: Tok<String>,
discr: IStr,
tail: ParsSnippet<'a>,
) -> OrcRes<Vec<Item>> {
let kind = if discr == ctx.i().i("mod").await {
let kind = if discr == is("mod").await {
let (name, body) = parse_module(ctx, path, tail).await?;
ItemKind::Member(ParsedMember { name, exported, kind: ParsedMemberKind::Mod(body) })
} else if let Some(parser) = ctx.systems().find_map(|s| s.get_parser(discr.clone())) {
@@ -127,7 +116,7 @@ pub async fn parse_exportable_item<'a>(
} else {
let ext_lines = ctx.systems().flat_map(System::line_types).join(", ");
return Err(mk_errv(
ctx.i().i("Unrecognized line type").await,
is("Unrecognized line type").await,
format!("Line types are: mod, {ext_lines}"),
[tail.prev().sr()],
));
@@ -137,25 +126,25 @@ pub async fn parse_exportable_item<'a>(
pub async fn parse_module<'a>(
ctx: &impl HostParseCtx,
path: Substack<'_, Tok<String>>,
path: Substack<'_, IStr>,
tail: ParsSnippet<'a>,
) -> OrcRes<(Tok<String>, ParsedModule)> {
let (name, tail) = match try_pop_no_fluff(ctx, tail).await? {
) -> OrcRes<(IStr, ParsedModule)> {
let (name, tail) = match try_pop_no_fluff(tail).await? {
Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } => (n.clone(), tail),
Parsed { output, .. } => {
return Err(mk_errv(
ctx.i().i("Missing module name").await,
format!("A name was expected, {} was found", fmt(output, ctx.i()).await),
is("Missing module name").await,
format!("A name was expected, {} was found", fmt(output).await),
[output.sr()],
));
},
};
let Parsed { output, tail: surplus } = try_pop_no_fluff(ctx, tail).await?;
expect_end(ctx, surplus).await?;
let Parsed { output, tail: surplus } = try_pop_no_fluff(tail).await?;
expect_end(surplus).await?;
let Some(body) = output.as_s(Paren::Round) else {
return Err(mk_errv(
ctx.i().i("Expected module body").await,
format!("A ( block ) was expected, {} was found", fmt(output, ctx.i()).await),
is("Expected module body").await,
format!("A ( block ) was expected, {} was found", fmt(output).await),
[output.sr()],
));
};

View File

@@ -6,7 +6,7 @@ use futures::future::{LocalBoxFuture, join_all};
use hashbrown::HashSet;
use itertools::Itertools;
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
use orchid_base::interner::Tok;
use orchid_base::interner::{IStr, IStrv};
use orchid_base::location::SrcRange;
use orchid_base::parse::{Comment, Import};
use orchid_base::tl_cache;
@@ -57,10 +57,10 @@ impl Format for Item {
ItemKind::Member(mem) => match &mem.kind {
ParsedMemberKind::Const(_, sys) =>
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("const {0} via {1}")))
.units([mem.name.rc().into(), sys.print(c).await]),
.units([mem.name.to_string().into(), sys.print(c).await]),
ParsedMemberKind::Mod(module) =>
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("module {0} {{\n\t{1}\n}}")))
.units([mem.name.rc().into(), module.print(c).boxed_local().await]),
.units([mem.name.to_string().into(), module.print(c).boxed_local().await]),
},
};
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("{0}\n{1}")))
@@ -69,14 +69,14 @@ impl Format for Item {
}
pub struct ParsedMember {
pub name: Tok<String>,
pub name: IStr,
pub exported: bool,
pub kind: ParsedMemberKind,
}
impl ParsedMember {
#[must_use]
pub fn name(&self) -> Tok<String> { self.name.clone() }
pub fn new(exported: bool, name: Tok<String>, kind: impl Into<ParsedMemberKind>) -> Self {
pub fn name(&self) -> IStr { self.name.clone() }
pub fn new(exported: bool, name: IStr, kind: impl Into<ParsedMemberKind>) -> Self {
Self { exported, name, kind: kind.into() }
}
}
@@ -89,17 +89,14 @@ impl Debug for ParsedMember {
}
}
pub(crate) type ParsedExprCallback =
Rc<dyn for<'a> Fn(&'a [Tok<String>]) -> LocalBoxFuture<'a, Expr>>;
pub(crate) type ParsedExprCallback = Rc<dyn for<'a> Fn(&'a [IStr]) -> LocalBoxFuture<'a, Expr>>;
pub struct ParsedExpr {
pub(crate) debug: String,
pub(crate) callback: ParsedExprCallback,
}
impl ParsedExpr {
pub async fn run(self, imported_names: &[Tok<String>]) -> Expr {
(self.callback)(imported_names).await
}
pub async fn run(self, imported_names: &[IStr]) -> Expr { (self.callback)(imported_names).await }
}
impl fmt::Debug for ParsedExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.debug) }
@@ -115,7 +112,7 @@ impl From<ParsedModule> for ParsedMemberKind {
}
#[derive(Debug, Default)]
pub struct ParsedModule {
pub exports: Vec<Tok<String>>,
pub exports: Vec<IStr>,
pub items: Vec<Item>,
pub use_prelude: bool,
}
@@ -141,7 +138,7 @@ impl ParsedModule {
(self.items.iter())
.filter_map(|it| if let ItemKind::Import(i) = &it.kind { Some(i) } else { None })
}
pub fn default_item(self, name: Tok<String>, sr: SrcRange) -> Item {
pub fn default_item(self, name: IStr, sr: SrcRange) -> Item {
let mem = ParsedMember { exported: true, name, kind: ParsedMemberKind::Mod(self) };
Item { comments: vec![], sr, kind: ItemKind::Member(mem) }
}
@@ -150,7 +147,7 @@ impl Tree for ParsedModule {
type Ctx<'a> = ();
async fn child(
&self,
key: Tok<String>,
key: IStr,
public_only: bool,
(): &mut Self::Ctx<'_>,
) -> ChildResult<'_, Self> {
@@ -168,7 +165,7 @@ impl Tree for ParsedModule {
}
ChildResult::Err(ChildErrorKind::Missing)
}
fn children(&self, public_only: bool) -> HashSet<Tok<String>> {
fn children(&self, public_only: bool) -> HashSet<IStr> {
let mut public: HashSet<_> = self.exports.iter().cloned().collect();
if !public_only {
public.extend(
@@ -197,11 +194,11 @@ impl Format for ParsedModule {
/// point to a module and rule_loc selects a macro rule within that module
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ConstPath {
steps: Tok<Vec<Tok<String>>>,
steps: IStrv,
}
impl ConstPath {
#[must_use]
pub fn to_const(steps: Tok<Vec<Tok<String>>>) -> Self { Self { steps } }
pub fn to_const(steps: IStrv) -> Self { Self { steps } }
}
pub async fn tt_to_api(exprs: &mut ExprStore, subtree: ParsTokTree) -> api::TokenTree {

View File

@@ -1,102 +1,32 @@
use std::cell::RefCell;
use std::io::{self, Write};
use std::pin::Pin;
use std::io;
use async_process::{self, Child, ChildStdin, ChildStdout};
use futures::future::LocalBoxFuture;
use async_process;
use futures::io::BufReader;
use futures::lock::Mutex;
use futures::{self, AsyncBufReadExt, AsyncWriteExt};
use orchid_api_traits::{Decode, Encode};
use orchid_base::builtin::{ExtInit, ExtPort};
use orchid_base::logging::Logger;
use orchid_base::msg::{recv_msg, send_msg};
use futures::{self, AsyncBufReadExt};
use orchid_base::logging::logger;
use crate::api;
use crate::ctx::Ctx;
use crate::extension::ExtPort;
pub async fn ext_command(
cmd: std::process::Command,
logger: Logger,
msg_logs: Logger,
ctx: Ctx,
) -> io::Result<ExtInit> {
pub async fn ext_command(cmd: std::process::Command, ctx: Ctx) -> io::Result<ExtPort> {
let mut child = async_process::Command::from(cmd)
.stdin(async_process::Stdio::piped())
.stdout(async_process::Stdio::piped())
.stderr(async_process::Stdio::piped())
.spawn()?;
let mut stdin = child.stdin.take().unwrap();
api::HostHeader { log_strategy: logger.strat(), msg_logs: msg_logs.strat() }
.encode(Pin::new(&mut stdin))
.await;
let mut stdout = child.stdout.take().unwrap();
let header = api::ExtensionHeader::decode(Pin::new(&mut stdout)).await;
let stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
let mut child_stderr = child.stderr.take().unwrap();
(ctx.spawn)(Box::pin(async move {
let _ = ctx.spawn(Box::pin(async move {
let _ = child;
let mut reader = BufReader::new(&mut child_stderr);
loop {
let mut buf = String::new();
if 0 == reader.read_line(&mut buf).await.unwrap() {
break;
}
logger.log(buf.strip_suffix('\n').expect("Readline implies this"));
logger().log(buf.strip_suffix('\n').expect("Readline implies this"));
}
}));
Ok(ExtInit {
port: Box::new(Subprocess {
name: header.name.clone(),
child: RefCell::new(Some(child)),
stdin: Some(Mutex::new(Box::pin(stdin))),
stdout: Mutex::new(Box::pin(stdout)),
ctx,
}),
header,
})
}
pub struct Subprocess {
name: String,
child: RefCell<Option<Child>>,
stdin: Option<Mutex<Pin<Box<ChildStdin>>>>,
stdout: Mutex<Pin<Box<ChildStdout>>>,
ctx: Ctx,
}
impl Drop for Subprocess {
fn drop(&mut self) {
let mut child = self.child.borrow_mut().take().unwrap();
let name = self.name.clone();
if std::thread::panicking() {
eprintln!("Killing extension {name}");
// we don't really care to handle errors here
let _: Result<_, _> = std::io::stderr().flush();
let _: Result<_, _> = child.kill();
return;
}
let stdin = self.stdin.take().unwrap();
(self.ctx.spawn)(Box::pin(async move {
stdin.lock().await.close().await.unwrap();
let status = (child.status().await)
.unwrap_or_else(|e| panic!("{e}, extension {name} exited with error"));
assert!(status.success(), "Extension {name} exited with error {status}");
}))
}
}
impl ExtPort for Subprocess {
fn send<'a>(&'a self, msg: &'a [u8]) -> LocalBoxFuture<'a, ()> {
Box::pin(async {
send_msg(Pin::new(&mut *self.stdin.as_ref().unwrap().lock().await), msg).await.unwrap()
})
}
fn recv(&self) -> LocalBoxFuture<'_, Option<Vec<u8>>> {
Box::pin(async {
std::io::Write::flush(&mut std::io::stderr()).unwrap();
match recv_msg(self.stdout.lock().await.as_mut()).await {
Ok(msg) => Some(msg),
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => None,
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => None,
Err(e) => panic!("Failed to read from stdout: {}, {e}", e.kind()),
}
})
}
Ok(ExtPort { input: Box::pin(stdin), output: Box::pin(stdout) })
}

View File

@@ -2,11 +2,11 @@ use futures::FutureExt;
use futures::future::join_all;
use itertools::Itertools;
use orchid_base::error::{OrcErrv, OrcRes};
use orchid_base::interner::{Interner, Tok};
use orchid_base::interner::{IStr, es};
use orchid_base::location::SrcRange;
use orchid_base::name::Sym;
use orchid_base::parse::Comment;
use orchid_base::reqnot::Requester;
use orchid_base::reqnot::ClientExt;
use orchid_base::tree::ttv_from_api;
use substack::Substack;
@@ -22,7 +22,7 @@ pub struct Parser {
pub(crate) system: System,
pub(crate) idx: u16,
}
type ModPath<'a> = Substack<'a, Tok<String>>;
type ModPath<'a> = Substack<'a, IStr>;
impl Parser {
pub async fn parse(
@@ -39,7 +39,7 @@ impl Parser {
let line =
join_all((line.into_iter()).map(|t| async { tt_to_api(&mut temp_store.clone(), t).await }))
.await;
let mod_path = ctx.src_path().suffix(path.unreverse(), self.system.i()).await;
let mod_path = ctx.src_path().suffix(path.unreverse()).await;
let comments = comments.iter().map(Comment::to_api).collect_vec();
let req = api::ParseLine {
idx: self.idx,
@@ -50,17 +50,16 @@ impl Parser {
comments,
line,
};
match self.system.reqnot().request(req).await {
match self.system.client().request(req).await.unwrap() {
Ok(parsed_v) =>
conv(parsed_v, path, callback, &mut ConvCtx {
i: self.system.i(),
mod_path: &mod_path,
ext_exprs: &mut temp_store,
src_path: &src_path,
sys: &self.system,
})
.await,
Err(e) => Err(OrcErrv::from_api(&e, &self.system.ctx().i).await),
Err(e) => Err(OrcErrv::from_api(&e).await),
}
}
}
@@ -69,13 +68,12 @@ struct ConvCtx<'a> {
sys: &'a System,
mod_path: &'a Sym,
src_path: &'a Sym,
i: &'a Interner,
ext_exprs: &'a mut ExprStore,
}
async fn conv(
parsed_v: Vec<api::ParsedLine>,
module: Substack<'_, Tok<String>>,
callback: &'_ mut impl AsyncFnMut(Substack<'_, Tok<String>>, Vec<ParsTokTree>) -> OrcRes<Vec<Item>>,
module: Substack<'_, IStr>,
callback: &'_ mut impl AsyncFnMut(Substack<'_, IStr>, Vec<ParsTokTree>) -> OrcRes<Vec<Item>>,
ctx: &mut ConvCtx<'_>,
) -> OrcRes<Vec<Item>> {
let mut items = Vec::new();
@@ -85,12 +83,12 @@ async fn conv(
(name, exported, kind),
api::ParsedLineKind::Recursive(rec) => {
let tokens =
ttv_from_api(rec, ctx.ext_exprs, &mut ctx.sys.ctx().clone(), ctx.src_path, ctx.i).await;
ttv_from_api(rec, ctx.ext_exprs, &mut ctx.sys.ctx().clone(), ctx.src_path).await;
items.extend(callback(module.clone(), tokens).await?);
continue;
},
};
let name = ctx.i.ex(name).await;
let name = es(name).await;
let mem_path = module.push(name.clone());
let mkind = match kind {
api::ParsedMemberKind::Module { lines, use_prelude } => {
@@ -98,16 +96,16 @@ async fn conv(
ParsedMemberKind::Mod(ParsedModule::new(use_prelude, items))
},
api::ParsedMemberKind::Constant(cid) => {
ctx.sys.0.const_paths.insert(cid, ctx.mod_path.suffix(mem_path.unreverse(), ctx.i).await);
ctx.sys.0.const_paths.insert(cid, ctx.mod_path.suffix(mem_path.unreverse()).await);
ParsedMemberKind::Const(cid, ctx.sys.clone())
},
};
items.push(Item {
comments: join_all(
parsed.comments.iter().map(|c| Comment::from_api(c, ctx.src_path.clone(), ctx.i)),
parsed.comments.iter().map(|c| Comment::from_api(c, ctx.src_path.clone())),
)
.await,
sr: SrcRange::from_api(&parsed.source_range, ctx.i).await,
sr: SrcRange::from_api(&parsed.source_range).await,
kind: ItemKind::Member(ParsedMember { name, exported, kind: mkind }),
})
}

View File

@@ -12,10 +12,11 @@ use memo_map::MemoMap;
use orchid_base::char_filter::char_filter_match;
use orchid_base::error::{OrcRes, mk_errv_floating};
use orchid_base::format::{FmtCtx, FmtUnit, Format};
use orchid_base::interner::{Interner, Tok};
use orchid_base::interner::{IStr, es, is};
use orchid_base::iter_utils::IteratorPrint;
use orchid_base::logging::logger;
use orchid_base::name::{NameLike, Sym, VName, VPath};
use orchid_base::reqnot::{ReqNot, Requester};
use orchid_base::reqnot::{Client, ClientExt};
use ordered_float::NotNan;
use substack::{Stackframe, Substack};
@@ -35,7 +36,7 @@ pub(crate) struct SystemInstData {
decl_id: api::SysDeclId,
lex_filter: api::CharFilter,
id: api::SysId,
line_types: Vec<Tok<String>>,
line_types: Vec<IStr>,
prelude: Vec<Sym>,
owned_atoms: RwLock<HashMap<api::AtomId, WeakAtomHand>>,
pub(crate) const_paths: MemoMap<api::ParsedConstId, Sym>,
@@ -68,8 +69,6 @@ impl System {
#[must_use]
pub fn ctx(&self) -> &Ctx { &self.0.ctx }
#[must_use]
pub fn i(&self) -> &Interner { &self.0.ctx.i }
#[must_use]
pub fn deps(&self) -> &[System] { &self.0.deps }
#[must_use]
pub fn ctor(&self) -> SystemCtor {
@@ -77,22 +76,26 @@ impl System {
.expect("Ctor was used to create ext")
}
#[must_use]
pub(crate) fn reqnot(&self) -> &ReqNot<api::HostMsgSet> { self.0.ext.reqnot() }
pub(crate) fn client(&self) -> &dyn Client { self.0.ext.client() }
#[must_use]
pub async fn get_tree(&self, id: api::TreeId) -> api::MemberKind {
self.reqnot().request(api::GetMember(self.0.id, id)).await
self.client().request(api::GetMember(self.0.id, id)).await.unwrap()
}
#[must_use]
pub fn has_lexer(&self) -> bool { !self.0.lex_filter.0.is_empty() }
#[must_use]
pub fn can_lex(&self, c: char) -> bool { char_filter_match(&self.0.lex_filter, c) }
pub fn can_lex(&self, c: char) -> bool {
let ret = char_filter_match(&self.0.lex_filter, c);
writeln!(logger(), "{} can lex {c}: {}", self.ctor().name(), ret);
ret
}
#[must_use]
pub fn prelude(&self) -> Vec<Sym> { self.0.prelude.clone() }
/// Have this system lex a part of the source. It is assumed that
/// [Self::can_lex] was called and returned true.
pub async fn lex<F: Future<Output = Option<api::SubLexed>>>(
&self,
source: Tok<String>,
source: IStr,
src: Sym,
pos: u32,
r: impl FnMut(u32) -> F,
@@ -100,16 +103,16 @@ impl System {
self.0.ext.lex_req(source, src, pos, self.id(), r).await
}
#[must_use]
pub fn get_parser(&self, ltyp: Tok<String>) -> Option<Parser> {
pub fn get_parser(&self, ltyp: IStr) -> Option<Parser> {
(self.0.line_types.iter().enumerate())
.find(|(_, txt)| *txt == &ltyp)
.map(|(idx, _)| Parser { idx: idx as u16, system: self.clone() })
}
pub fn line_types(&self) -> impl Iterator<Item = &Tok<String>> + '_ { self.0.line_types.iter() }
pub fn line_types(&self) -> impl Iterator<Item = &IStr> + '_ { self.0.line_types.iter() }
#[must_use]
pub async fn request(&self, req: Vec<u8>) -> Vec<u8> {
self.reqnot().request(api::SysFwded(self.id(), req)).await
self.client().request(api::SysFwded(self.id(), req)).await.unwrap()
}
pub(crate) async fn new_atom(&self, data: Vec<u8>, id: api::AtomId) -> AtomHand {
let mut owned_g = self.0.owned_atoms.write().await;
@@ -124,10 +127,10 @@ impl System {
}
pub(crate) fn drop_atom(&self, dropped_atom_id: api::AtomId) {
let this = self.0.clone();
(self.0.ctx.spawn)(Box::pin(async move {
this.ext.reqnot().request(api::AtomDrop(this.id, dropped_atom_id)).await;
let _ = self.0.ctx.spawn(Box::pin(async move {
this.ext.client().request(api::AtomDrop(this.id, dropped_atom_id)).await.unwrap();
this.owned_atoms.write().await.remove(&dropped_atom_id);
}))
}));
}
#[must_use]
pub fn downgrade(&self) -> WeakSystem {
@@ -137,7 +140,7 @@ impl System {
pub(crate) async fn name_resolver(
&self,
orig: api::ParsedConstId,
) -> impl AsyncFnMut(&[Tok<String>]) -> OrcRes<VName> + use<> {
) -> impl AsyncFnMut(&[IStr]) -> OrcRes<VName> + use<> {
let root = self.0.ctx.root.read().await.upgrade().expect("find_names when root not in context");
let orig = self.0.const_paths.get(&orig).expect("origin for find_names invalid").clone();
let ctx = self.0.ctx.clone();
@@ -155,7 +158,7 @@ impl System {
Some(Ok(dest)) => return Ok(dest.target.to_vname().suffix(tail.iter().cloned())),
Some(Err(dests)) =>
return Err(mk_errv_floating(
ctx.i.i("Ambiguous name").await,
is("Ambiguous name").await,
format!(
"{selector} could refer to {}",
dests.iter().map(|ri| &ri.target).display("or")
@@ -170,7 +173,7 @@ impl System {
return Ok(VPath::new(cwd.iter().cloned()).name_with_suffix(selector.clone()));
}
Err(mk_errv_floating(
ctx.i.i("Invalid name").await,
is("Invalid name").await,
format!("{selector} doesn't refer to a module"),
))
}
@@ -203,8 +206,7 @@ impl SystemCtor {
#[must_use]
pub fn name(&self) -> &str { &self.decl.name }
pub async fn name_tok(&self) -> Sym {
(Sym::parse(&self.decl.name, &self.ext.upgrade().expect("ext dropped early").ctx().i).await)
.expect("System cannot have empty name")
(Sym::parse(&self.decl.name).await).expect("System cannot have empty name")
}
#[must_use]
pub fn priority(&self) -> NotNan<f64> { self.decl.priority }
@@ -220,17 +222,17 @@ impl SystemCtor {
debug_assert_eq!(depends.len(), self.decl.depends.len(), "Wrong number of deps provided");
let ext = self.ext.upgrade().expect("SystemCtor should be freed before Extension");
let id = ext.ctx().next_sys_id();
let sys_inst = ext.reqnot().request(api::NewSystem { depends, id, system: self.decl.id }).await;
let sys_inst =
ext.client().request(api::NewSystem { depends, id, system: self.decl.id }).await.unwrap();
let data = System(Rc::new(SystemInstData {
deps,
decl_id: self.decl.id,
ext: ext.clone(),
ctx: ext.ctx().clone(),
lex_filter: sys_inst.lex_filter,
line_types: join_all(sys_inst.line_types.iter().map(|m| Tok::from_api(*m, &ext.ctx().i)))
.await,
line_types: join_all(sys_inst.line_types.iter().map(|m| es(*m))).await,
id,
prelude: join_all(sys_inst.prelude.iter().map(|tok| Sym::from_api(*tok, &ext.ctx().i))).await,
prelude: join_all(sys_inst.prelude.iter().map(|tok| Sym::from_api(*tok))).await,
owned_atoms: RwLock::new(HashMap::new()),
const_paths: MemoMap::new(),
}));

View File

@@ -13,11 +13,11 @@ use hashbrown::hash_map::Entry;
use itertools::Itertools;
use memo_map::MemoMap;
use orchid_base::clone;
use orchid_base::error::{OrcRes, Reporter, mk_errv};
use orchid_base::interner::Tok;
use orchid_base::error::{OrcRes, mk_errv, report};
use orchid_base::interner::{IStr, IStrv, es, is, iv};
use orchid_base::location::{CodeGenInfo, Pos};
use orchid_base::name::{NameLike, Sym, VPath};
use orchid_base::reqnot::Requester;
use orchid_base::reqnot::ClientExt;
use crate::api;
use crate::ctx::Ctx;
@@ -45,7 +45,7 @@ impl Root {
#[must_use]
pub async fn from_api(api: api::Module, sys: &System) -> Self {
let consts = MemoMap::new();
let mut tfac = TreeFromApiCtx { consts: &consts, path: sys.i().i(&[][..]).await, sys };
let mut tfac = TreeFromApiCtx { consts: &consts, path: iv(&[][..]).await, sys };
let root = Module::from_api(api, &mut tfac).await;
Root(Rc::new(RwLock::new(RootData { root, consts, ctx: sys.ctx().clone() })))
}
@@ -60,7 +60,7 @@ impl Root {
Ok(Self(Rc::new(RwLock::new(RootData { root, consts, ctx: this.ctx.clone() }))))
}
#[must_use]
pub async fn add_parsed(&self, parsed: &ParsedModule, pars_prefix: Sym, rep: &Reporter) -> Self {
pub async fn add_parsed(&self, parsed: &ParsedModule, pars_prefix: Sym) -> Self {
let mut ref_this = self.0.write().await;
let this = &mut *ref_this;
let mut deferred_consts = HashMap::new();
@@ -72,7 +72,6 @@ impl Root {
pars_prefix: pars_prefix.clone(),
root: &this.root,
ctx: &this.ctx,
rep,
};
let mut module = Module::from_parsed(parsed, pars_prefix.clone(), &mut tfpctx).await;
for step in pars_prefix.iter().rev() {
@@ -89,7 +88,7 @@ impl Root {
*this.ctx.root.write().await = new.downgrade();
for (path, (sys_id, pc_id)) in deferred_consts {
let sys = this.ctx.system_inst(sys_id).await.expect("System dropped since parsing");
let api_expr = sys.reqnot().request(api::FetchParsedConst(sys.id(), pc_id)).await;
let api_expr = sys.client().request(api::FetchParsedConst(sys.id(), pc_id)).await.unwrap();
let expr = Expr::from_api(&api_expr, PathSetBuilder::new(), this.ctx.clone()).await;
new.0.write().await.consts.insert(path, expr);
}
@@ -110,7 +109,7 @@ impl Root {
}
match module {
Ok(_) => Err(mk_errv(
ctx.i.i("module used as constant").await,
is("module used as constant").await,
format!("{name} is a module, not a constant"),
[pos],
)),
@@ -118,7 +117,7 @@ impl Root {
ChildErrorKind::Private => panic!("public_only is false"),
ChildErrorKind::Constant => panic!("Tree refers to constant not in table"),
ChildErrorKind::Missing => Err(mk_errv(
ctx.i.i("Constant does not exist").await,
is("Constant does not exist").await,
format!("{name} does not refer to a constant"),
[pos],
)),
@@ -144,12 +143,12 @@ impl Default for WeakRoot {
pub struct TreeFromApiCtx<'a> {
pub sys: &'a System,
pub consts: &'a MemoMap<Sym, Expr>,
pub path: Tok<Vec<Tok<String>>>,
pub path: IStrv,
}
impl<'a> TreeFromApiCtx<'a> {
#[must_use]
pub async fn push<'c>(&'c self, name: Tok<String>) -> TreeFromApiCtx<'c> {
let path = self.sys.ctx().i.i(&self.path.iter().cloned().chain([name]).collect_vec()).await;
pub async fn push<'c>(&'c self, name: IStr) -> TreeFromApiCtx<'c> {
let path = iv(&self.path.iter().cloned().chain([name]).collect_vec()).await;
TreeFromApiCtx { path, consts: self.consts, sys: self.sys }
}
}
@@ -162,17 +161,17 @@ pub struct ResolvedImport {
#[derive(Clone, Default)]
pub struct Module {
pub imports: HashMap<Tok<String>, Result<ResolvedImport, Vec<ResolvedImport>>>,
pub members: HashMap<Tok<String>, Rc<Member>>,
pub imports: HashMap<IStr, Result<ResolvedImport, Vec<ResolvedImport>>>,
pub members: HashMap<IStr, Rc<Member>>,
}
impl Module {
#[must_use]
pub async fn from_api(api: api::Module, ctx: &mut TreeFromApiCtx<'_>) -> Self {
let mut members = HashMap::new();
for mem in api.members {
let mem_name = ctx.sys.i().ex(mem.name).await;
let mem_name = es(mem.name).await;
let vname = VPath::new(ctx.path.iter().cloned()).name_with_suffix(mem_name.clone());
let name = vname.to_sym(ctx.sys.i()).await;
let name = vname.to_sym().await;
let (lazy, kind) = match mem.kind {
api::MemberKind::Lazy(id) =>
(Some(LazyMemberHandle { id, sys: ctx.sys.id(), path: name.clone() }), None),
@@ -205,23 +204,23 @@ impl Module {
let mut glob_imports_by_name = HashMap::<_, Vec<_>>::new();
for import in parsed.get_imports().into_iter().filter(|i| i.name.is_none()) {
let pos = import.sr.pos();
match absolute_path(&path, &import.path, &ctx.ctx.i).await {
Err(e) => ctx.rep.report(e.err_obj(&ctx.ctx.i, pos, &import.path.to_string()).await),
match absolute_path(&path, &import.path).await {
Err(e) => report(e.err_obj(pos, &import.path.to_string()).await),
Ok(abs_path) => {
let names_res = match abs_path.strip_prefix(&ctx.pars_prefix[..]) {
None => {
let mut tree_ctx = (ctx.ctx.clone(), ctx.consts);
resolv_glob(&path, ctx.root, &abs_path, pos, &ctx.ctx.i, &mut tree_ctx).await
resolv_glob(&path, ctx.root, &abs_path, pos, &mut tree_ctx).await
},
Some(sub_tgt) => {
let sub_path = (path.strip_prefix(&ctx.pars_prefix[..]))
.expect("from_parsed called with path outside pars_prefix");
resolv_glob(sub_path, ctx.pars_root, sub_tgt, pos, &ctx.ctx.i, &mut ()).await
resolv_glob(sub_path, ctx.pars_root, sub_tgt, pos, &mut ()).await
},
};
let abs_path = abs_path.to_sym(&ctx.ctx.i).await;
let abs_path = abs_path.to_sym().await;
match names_res {
Err(e) => ctx.rep.report(e),
Err(e) => report(e),
Ok(names) =>
for name in names {
match glob_imports_by_name.entry(name) {
@@ -244,30 +243,28 @@ impl Module {
prelude_item.last_seg(),
Ok(ResolvedImport {
target: prelude_item,
pos: CodeGenInfo::new_details(sys.ctor().name_tok().await, "In prelude", &ctx.ctx.i)
.await
.pos(),
pos: CodeGenInfo::new_details(sys.ctor().name_tok().await, "In prelude").await.pos(),
}),
);
}
}
}
let conflicting_imports_msg = ctx.ctx.i.i("Conflicting imports").await;
let conflicting_imports_msg = is("Conflicting imports").await;
for (key, values) in imports_by_name {
if values.len() == 1 {
let import = values.into_iter().next().unwrap();
let sr = import.sr.clone();
let abs_path_res = absolute_path(&path, &import.clone().mspath(), &ctx.ctx.i).await;
let abs_path_res = absolute_path(&path, &import.clone().mspath()).await;
match abs_path_res {
Err(e) => ctx.rep.report(e.err_obj(&ctx.ctx.i, sr.pos(), &import.to_string()).await),
Err(e) => report(e.err_obj(sr.pos(), &import.to_string()).await),
Ok(abs_path) => {
let target = abs_path.to_sym(&ctx.ctx.i).await;
let target = abs_path.to_sym().await;
imports.insert(key, Ok(ResolvedImport { target, pos: sr.pos() }));
},
}
} else {
for item in values {
ctx.rep.report(mk_errv(
report(mk_errv(
conflicting_imports_msg.clone(),
format!("{key} is imported multiple times from different modules"),
[item.sr.pos()],
@@ -277,12 +274,11 @@ impl Module {
}
for (key, values) in glob_imports_by_name {
if !imports.contains_key(&key) {
let i = &ctx.ctx.i;
let values = stream::iter(values)
.then(|(n, sr)| {
clone!(key; async move {
ResolvedImport {
target: n.to_vname().suffix([key.clone()]).to_sym(i).await,
target: n.to_vname().suffix([key.clone()]).to_sym().await,
pos: sr.pos(),
}
})
@@ -292,12 +288,12 @@ impl Module {
imports.insert(key, if values.len() == 1 { Ok(values[0].clone()) } else { Err(values) });
}
}
let self_referential_msg = ctx.ctx.i.i("Self-referential import").await;
let self_referential_msg = is("Self-referential import").await;
for (key, value) in imports.iter() {
let Ok(import) = value else { continue };
if import.target.strip_prefix(&path[..]).is_some_and(|t| t.starts_with(slice::from_ref(key)))
{
ctx.rep.report(mk_errv(
report(mk_errv(
self_referential_msg.clone(),
format!("import {} points to itself or a path within itself", &import.target),
[import.pos.clone()],
@@ -308,7 +304,7 @@ impl Module {
for item in &parsed.items {
match &item.kind {
ItemKind::Member(mem) => {
let path = path.to_vname().suffix([mem.name.clone()]).to_sym(&ctx.ctx.i).await;
let path = path.to_vname().suffix([mem.name.clone()]).to_sym().await;
let kind = OnceCell::from(MemberKind::from_parsed(&mem.kind, path.clone(), ctx).await);
members.insert(
mem.name.clone(),
@@ -385,7 +381,6 @@ pub struct FromParsedCtx<'a> {
pars_prefix: Sym,
pars_root: &'a ParsedModule,
root: &'a Module,
rep: &'a Reporter,
ctx: &'a Ctx,
consts: &'a MemoMap<Sym, Expr>,
deferred_consts: &'a mut HashMap<Sym, (api::SysId, api::ParsedConstId)>,
@@ -395,7 +390,7 @@ impl Tree for Module {
type Ctx<'a> = (Ctx, &'a MemoMap<Sym, Expr>);
async fn child(
&self,
key: Tok<String>,
key: IStr,
public_only: bool,
(ctx, consts): &mut Self::Ctx<'_>,
) -> crate::dealias::ChildResult<'_, Self> {
@@ -410,7 +405,7 @@ impl Tree for Module {
MemberKind::Const => Err(ChildErrorKind::Constant),
}
}
fn children(&self, public_only: bool) -> hashbrown::HashSet<Tok<String>> {
fn children(&self, public_only: bool) -> hashbrown::HashSet<IStr> {
self.members.iter().filter(|(_, v)| !public_only || v.public).map(|(k, _)| k.clone()).collect()
}
}

View File

@@ -4,7 +4,6 @@ use never::Never;
use orchid_base::format::fmt;
use orchid_extension::atom::{Atomic, TAtom};
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own};
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::exec;
use orchid_extension::expr::Expr;
@@ -38,7 +37,7 @@ impl OwnedAtom for InstantiateTplCall {
async fn call(mut self, arg: Expr) -> GExpr {
exec(async move |mut h| {
match h.exec::<TAtom<MacTree>>(arg.clone()).await {
Err(_) => panic!("Expected a macro param, found {}", fmt(&arg, &i()).await),
Err(_) => panic!("Expected a macro param, found {}", fmt(&arg).await),
Ok(t) => self.argv.push(own(&t).await),
};
if self.argv.len() < self.argc {

View File

@@ -3,15 +3,13 @@ use std::pin::pin;
use futures::{FutureExt, StreamExt, stream};
use hashbrown::HashMap;
use itertools::Itertools;
use orchid_base::error::{OrcRes, Reporter};
use orchid_base::error::{OrcRes, report, with_reporter};
use orchid_base::interner::is;
use orchid_base::name::Sym;
use orchid_base::parse::{
Comment, ParseCtx, Parsed, Snippet, expect_tok, token_errv, try_pop_no_fluff,
};
use orchid_base::parse::{Comment, Parsed, Snippet, expect_tok, token_errv, try_pop_no_fluff};
use orchid_base::sym;
use orchid_base::tree::Paren;
use orchid_extension::atom::TAtom;
use orchid_extension::context::i;
use orchid_extension::conv::TryFromExpr;
use orchid_extension::gen_expr::{atom, call, sym_ref};
use orchid_extension::parser::{ConstCtx, PSnippet, PTok, PTokTree, ParsCtx, ParsedLine, Parser};
@@ -24,40 +22,36 @@ pub struct LetLine;
impl Parser for LetLine {
const LINE_HEAD: &'static str = "let";
async fn parse<'a>(
ctx: ParsCtx<'a>,
_: ParsCtx<'a>,
exported: bool,
comments: Vec<Comment>,
line: PSnippet<'a>,
) -> OrcRes<Vec<ParsedLine>> {
let sr = line.sr();
let Parsed { output: name_tok, tail } = try_pop_no_fluff(&ctx, line).await?;
let Parsed { output: name_tok, tail } = try_pop_no_fluff(line).await?;
let Some(name) = name_tok.as_name() else {
let err = token_errv(&ctx, name_tok, "Constant must have a name", |t| {
let err = token_errv(name_tok, "Constant must have a name", |t| {
format!("Expected a name but found {t}")
});
return Err(err.await);
};
let Parsed { tail, .. } = expect_tok(&ctx, tail, ctx.i().i("=").await).await?;
let aliased = parse_tokv(tail, &ctx).await;
let Parsed { tail, .. } = expect_tok(tail, is("=").await).await?;
let aliased = parse_tokv(tail).await;
Ok(vec![ParsedLine::cnst(&line.sr(), &comments, exported, name, async move |ctx| {
let rep = Reporter::new();
let macro_input =
MacTok::S(Paren::Round, dealias_mac_v(&aliased, &ctx, &rep).await).at(sr.pos());
if let Some(e) = rep.errv() {
return Err(e);
}
Ok(call(sym_ref(sym!(macros::resolve; i())), [atom(macro_input)]))
MacTok::S(Paren::Round, with_reporter(dealias_mac_v(&aliased, &ctx)).await?).at(sr.pos());
Ok(call(sym_ref(sym!(macros::resolve)), [atom(macro_input)]))
})])
}
}
pub async fn dealias_mac_v(aliased: &MacTreeSeq, ctx: &ConstCtx, rep: &Reporter) -> MacTreeSeq {
pub async fn dealias_mac_v(aliased: &MacTreeSeq, ctx: &ConstCtx) -> MacTreeSeq {
let keys = aliased.glossary().iter().cloned().collect_vec();
let mut names: HashMap<_, _> = HashMap::new();
let mut stream = pin!(ctx.names(&keys).zip(stream::iter(&keys)));
while let Some((canonical, local)) = stream.next().await {
match canonical {
Err(e) => rep.report(e),
Err(e) => report(e),
Ok(name) => {
names.insert(local.clone(), name);
},
@@ -69,16 +63,16 @@ pub async fn dealias_mac_v(aliased: &MacTreeSeq, ctx: &ConstCtx, rep: &Reporter)
})
}
pub async fn parse_tokv(line: PSnippet<'_>, ctx: &impl ParseCtx) -> MacTreeSeq {
pub async fn parse_tokv(line: PSnippet<'_>) -> MacTreeSeq {
if let Some((idx, arg)) = line.iter().enumerate().find_map(|(i, x)| Some((i, x.as_lambda()?))) {
let (head, lambda) = line.split_at(idx as u32);
let (_, body) = lambda.pop_front().unwrap();
let body = parse_tokv(body, ctx).boxed_local().await;
let mut all = parse_tokv_no_lambdas(&head, ctx).await;
match parse_tok(arg, ctx).await {
let body = parse_tokv(body).boxed_local().await;
let mut all = parse_tokv_no_lambdas(&head).await;
match parse_tok(arg).await {
Some(arg) => all.push(MacTok::Lambda(arg, body).at(lambda.sr().pos())),
None => ctx.rep().report(
token_errv(ctx, arg, "Lambda argument fluff", |arg| {
None => report(
token_errv(arg, "Lambda argument fluff", |arg| {
format!("Lambda arguments must be a valid token, found meaningless fragment {arg}")
})
.await,
@@ -86,29 +80,29 @@ pub async fn parse_tokv(line: PSnippet<'_>, ctx: &impl ParseCtx) -> MacTreeSeq {
};
MacTreeSeq::new(all)
} else {
MacTreeSeq::new(parse_tokv_no_lambdas(&line, ctx).await)
MacTreeSeq::new(parse_tokv_no_lambdas(&line).await)
}
}
async fn parse_tokv_no_lambdas(line: &[PTokTree], ctx: &impl ParseCtx) -> Vec<MacTree> {
stream::iter(line).filter_map(|tt| parse_tok(tt, ctx)).collect::<Vec<_>>().await
async fn parse_tokv_no_lambdas(line: &[PTokTree]) -> Vec<MacTree> {
stream::iter(line).filter_map(parse_tok).collect::<Vec<_>>().await
}
pub async fn parse_tok(tree: &PTokTree, ctx: &impl ParseCtx) -> Option<MacTree> {
pub async fn parse_tok(tree: &PTokTree) -> Option<MacTree> {
let tok = match &tree.tok {
PTok::Bottom(errv) => MacTok::Bottom(errv.clone()),
PTok::BR | PTok::Comment(_) => return None,
PTok::Name(n) => MacTok::Name(Sym::new([n.clone()], ctx.i()).await.unwrap()),
PTok::Name(n) => MacTok::Name(Sym::new([n.clone()]).await.unwrap()),
PTok::NS(..) => match tree.as_multiname() {
Ok(mn) => MacTok::Name(mn.to_sym(ctx.i()).await),
Ok(mn) => MacTok::Name(mn.to_sym().await),
Err(nested) => {
ctx.rep().report(
token_errv(ctx, tree, ":: can only be followed by a name in an expression", |tok| {
report(
token_errv(tree, ":: can only be followed by a name in an expression", |tok| {
format!("Expected name, found {tok}")
})
.await,
);
return parse_tok(nested, ctx).boxed_local().await;
return parse_tok(nested).boxed_local().await;
},
},
PTok::Handle(expr) => match TAtom::<PhAtom>::try_from_expr(expr.clone()).await {
@@ -117,8 +111,7 @@ pub async fn parse_tok(tree: &PTokTree, ctx: &impl ParseCtx) -> Option<MacTree>
},
PTok::NewExpr(never) => match *never {},
PTok::LambdaHead(_) => panic!("Lambda-head handled in the sequence parser"),
PTok::S(p, body) =>
MacTok::S(*p, parse_tokv(Snippet::new(tree, body), ctx).boxed_local().await),
PTok::S(p, body) => MacTok::S(*p, parse_tokv(Snippet::new(tree, body)).boxed_local().await),
};
Some(tok.at(tree.sr().pos()))
}

View File

@@ -1,7 +1,6 @@
use orchid_base::sym;
use orchid_extension::atom::TAtom;
use orchid_extension::atom_owned::own;
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::exec;
use orchid_extension::gen_expr::{call, sym_ref};
@@ -20,12 +19,12 @@ pub async fn gen_macro_lib() -> Vec<GenMember> {
build_macro(None, ["..", "_"]).finish(),
build_macro(Some(1), ["+"])
.rule(mactreev!("...$" lhs 0 macros::common::+ "...$" rhs 1), [async |[lhs, rhs]| {
call(sym_ref(sym!(std::number::add; i())), [resolve(lhs).await, resolve(rhs).await])
call(sym_ref(sym!(std::number::add)), [resolve(lhs).await, resolve(rhs).await])
}])
.finish(),
build_macro(Some(2), ["*"])
.rule(mactreev!("...$" lhs 0 macros::common::* "...$" rhs 1), [async |[lhs, rhs]| {
call(sym_ref(sym!(std::number::mul; i())), [resolve(lhs).await, resolve(rhs).await])
call(sym_ref(sym!(std::number::mul)), [resolve(lhs).await, resolve(rhs).await])
}])
.finish(),
build_macro(None, ["comma_list", ","])

View File

@@ -4,15 +4,14 @@ use async_fn_stream::stream;
use async_once_cell::OnceCell;
use futures::StreamExt;
use itertools::Itertools;
use orchid_base::error::{OrcRes, Reporter, mk_errv};
use orchid_base::error::{OrcRes, mk_errv, report, with_reporter};
use orchid_base::interner::is;
use orchid_base::parse::{
Comment, ParseCtx, Parsed, Snippet, expect_end, expect_tok, line_items, token_errv,
try_pop_no_fluff,
Comment, Parsed, Snippet, expect_end, expect_tok, line_items, token_errv, try_pop_no_fluff,
};
use orchid_base::tree::{Paren, Token};
use orchid_base::{clone, sym};
use orchid_extension::atom::TAtom;
use orchid_extension::context::i;
use orchid_extension::conv::{ToExpr, TryFromExpr};
use orchid_extension::gen_expr::{call, sym_ref};
use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser};
@@ -35,22 +34,22 @@ impl Parser for MacroLine {
) -> OrcRes<Vec<ParsedLine>> {
if exported {
return Err(mk_errv(
ctx.i().i("macros are always exported").await,
is("macros are always exported").await,
"The export keyword is forbidden here to avoid confusion\n\
because macros are exported by default",
[line.sr()],
));
}
let module = ctx.module();
let Parsed { output: prio_or_body, tail } = try_pop_no_fluff(&ctx, line).await?;
let Parsed { output: prio_or_body, tail } = try_pop_no_fluff(line).await?;
let bad_first_item_err = || {
token_errv(&ctx, prio_or_body, "Expected priority or block", |s| {
token_errv(prio_or_body, "Expected priority or block", |s| {
format!("Expected a priority number or a () block, found {s}")
})
};
let (prio, body) = match &prio_or_body.tok {
Token::S(Paren::Round, body) => {
expect_end(&ctx, tail).await?;
expect_end(tail).await?;
(None, body)
},
Token::Handle(expr) => match TAtom::<Int>::try_from_expr(expr.clone()).await {
@@ -58,33 +57,32 @@ impl Parser for MacroLine {
return Err(e + bad_first_item_err().await);
},
Ok(prio) => {
let Parsed { output: body, tail } = try_pop_no_fluff(&ctx, tail).await?;
let Parsed { output: body, tail } = try_pop_no_fluff(tail).await?;
let Token::S(Paren::Round, block) = &body.tok else {
return Err(
token_errv(&ctx, prio_or_body, "Expected () block", |s| {
token_errv(prio_or_body, "Expected () block", |s| {
format!("Expected a () block, found {s}")
})
.await,
);
};
expect_end(&ctx, tail).await?;
expect_end(tail).await?;
(Some(prio), block)
},
},
_ => return Err(bad_first_item_err().await),
};
let lines = line_items(&ctx, Snippet::new(prio_or_body, body)).await;
let lines = line_items(Snippet::new(prio_or_body, body)).await;
let Some((kw_line, rule_lines)) = lines.split_first() else { return Ok(Vec::new()) };
let mut keywords = Vec::new();
let Parsed { tail: kw_tail, .. } =
expect_tok(&ctx, kw_line.tail, ctx.i().i("keywords").await).await?;
let Parsed { tail: kw_tail, .. } = expect_tok(kw_line.tail, is("keywords").await).await?;
for kw_tok in kw_tail.iter().filter(|kw| !kw.is_fluff()) {
match kw_tok.as_name() {
Some(kw) => {
keywords.push((kw, kw_tok.sr()));
},
None => ctx.rep().report(
token_errv(&ctx, kw_tok, "invalid macro keywords list", |tok| {
None => report(
token_errv(kw_tok, "invalid macro keywords list", |tok| {
format!("The keywords list must be a sequence of names; received {tok}")
})
.await,
@@ -93,7 +91,7 @@ impl Parser for MacroLine {
}
let Some((macro_name, _)) = keywords.first().cloned() else {
return Err(mk_errv(
ctx.i().i("macro with no keywords").await,
is("macro with no keywords").await,
"Macros must define at least one macro of their own.",
[kw_line.tail.sr()],
));
@@ -102,18 +100,18 @@ impl Parser for MacroLine {
let mut lines = Vec::new();
for (idx, line) in rule_lines.iter().enumerate().map(|(n, v)| (n as u32, v)) {
let sr = line.tail.sr();
let name = ctx.i().i(&format!("rule::{}::{}", macro_name, idx)).await;
let Parsed { tail, .. } = expect_tok(&ctx, line.tail, ctx.i().i("rule").await).await?;
let arrow_token = ctx.i().i("=>").await;
let name = is(&format!("rule::{}::{}", macro_name, idx)).await;
let Parsed { tail, .. } = expect_tok(line.tail, is("rule").await).await?;
let arrow_token = is("=>").await;
let Some((pattern, body)) = tail.split_once(|tok| tok.is_kw(arrow_token.clone())) else {
ctx.rep().report(mk_errv(
ctx.i().i("Missing => in rule").await,
report(mk_errv(
is("Missing => in rule").await,
"Rule lines are of the form `rule ...pattern => ...body`",
[line.tail.sr()],
));
continue;
};
let pattern = parse_tokv(pattern, &ctx).await;
let pattern = parse_tokv(pattern).await;
let mut placeholders = Vec::new();
pattern.map(&mut false, &mut |tok| {
if let MacTok::Ph(ph) = tok.tok() {
@@ -121,9 +119,9 @@ impl Parser for MacroLine {
}
None
});
let mut body_mactree = parse_tokv(body, &ctx).await;
let mut body_mactree = parse_tokv(body).await;
for (ph, ph_pos) in placeholders.iter().rev() {
let name = ctx.module().suffix([ph.name.clone()], ctx.i()).await;
let name = ctx.module().suffix([ph.name.clone()]).await;
body_mactree =
MacTreeSeq::new([
MacTok::Lambda(MacTok::Name(name).at(ph_pos.clone()), body_mactree).at(ph_pos.clone())
@@ -132,45 +130,40 @@ impl Parser for MacroLine {
let body_sr = body.sr();
rules.push((name.clone(), placeholders, pattern));
lines.push(ParsedLine::cnst(&sr, &line.output, true, name, async move |ctx| {
let rep = Reporter::new();
let body = dealias_mac_v(&body_mactree, &ctx, &rep).await;
let macro_input = MacTok::S(Paren::Round, body).at(body_sr.pos());
if let Some(e) = rep.errv() {
return Err(e);
}
Ok(call(sym_ref(sym!(macros::resolve; i())), [macro_input.to_gen().await]))
let macro_input =
MacTok::S(Paren::Round, with_reporter(dealias_mac_v(&body_mactree, &ctx)).await?)
.at(body_sr.pos());
Ok(call(sym_ref(sym!(macros::resolve)), [macro_input.to_gen().await]))
}))
}
let mac_cell = Rc::new(OnceCell::new());
let rules = Rc::new(rules);
for (kw, sr) in &*keywords {
clone!(mac_cell, rules, module, macro_name, prio);
let kw_key = i().i(&format!("__macro__{kw}")).await;
let kw_key = is(&format!("__macro__{kw}")).await;
lines.push(ParsedLine::cnst(&sr.clone(), &comments, true, kw_key, async move |cctx| {
let mac_future = async {
let rep = Reporter::new();
let rules = stream(async |mut h| {
for (body, ph_names, pattern_rel) in rules.iter() {
let pattern = dealias_mac_v(pattern_rel, &cctx, &rep).await;
let ph_names = ph_names.iter().map(|(ph, _)| ph.name.clone()).collect_vec();
match Matcher::new(pattern.clone()).await {
Ok(matcher) =>
h.emit(Rule { body: body.clone(), matcher, pattern, ph_names }).await,
Err(e) => rep.report(e),
let rules = with_reporter(
stream(async |mut h| {
for (body, ph_names, pattern_rel) in rules.iter() {
let pattern = dealias_mac_v(pattern_rel, &cctx).await;
let ph_names = ph_names.iter().map(|(ph, _)| ph.name.clone()).collect_vec();
match Matcher::new(pattern.clone()).await {
Ok(matcher) =>
h.emit(Rule { body: body.clone(), matcher, pattern, ph_names }).await,
Err(e) => report(e),
}
}
}
})
.collect::<Vec<_>>()
.await;
match rep.errv() {
Some(e) => Err(e),
None => Ok(Macro(Rc::new(MacroData {
canonical_name: module.suffix([macro_name], &i()).await,
module,
prio: prio.map(|i| i.0 as u64),
rules,
}))),
}
})
.collect::<Vec<_>>(),
)
.await?;
Ok(Macro(Rc::new(MacroData {
canonical_name: module.suffix([macro_name]).await,
module,
prio: prio.map(|i| i.0 as u64),
rules,
})))
};
mac_cell.get_or_init(mac_future).await.clone().to_gen().await
}))

View File

@@ -1,10 +1,8 @@
use never::Never;
use orchid_base::name::Sym;
use orchid_base::reqnot::Receipt;
use orchid_base::reqnot::{Receipt, ReqHandle};
use orchid_base::sym;
use orchid_extension::atom::{AtomDynfo, AtomicFeatures};
use orchid_extension::context::i;
use orchid_extension::entrypoint::ExtReq;
use orchid_extension::lexer::LexerObj;
use orchid_extension::other_system::SystemHandle;
use orchid_extension::parser::ParserObj;
@@ -24,14 +22,14 @@ use crate::macros::std_macros::gen_std_macro_lib;
use crate::macros::utils::MacroBodyArgCollector;
use crate::{MacTree, StdSystem};
#[derive(Default)]
#[derive(Debug, Default)]
pub struct MacroSystem;
impl SystemCtor for MacroSystem {
type Deps = StdSystem;
type Instance = Self;
const NAME: &'static str = "orchid::macros";
const VERSION: f64 = 0.00_01;
fn inst(_: SystemHandle<StdSystem>) -> Self::Instance { Self }
fn inst(&self, _: SystemHandle<StdSystem>) -> Self::Instance { Self }
}
impl SystemCard for MacroSystem {
type Ctor = Self;
@@ -48,19 +46,19 @@ impl SystemCard for MacroSystem {
}
}
impl System for MacroSystem {
async fn request(_: ExtReq<'_>, req: Never) -> Receipt<'_> { match req {} }
async fn request<'a>(_: Box<dyn ReqHandle<'a> + 'a>, req: Never) -> Receipt<'a> { match req {} }
async fn prelude() -> Vec<Sym> {
vec![
sym!(macros::common::+; i()),
sym!(macros::common::*; i()),
sym!(macros::common::,; i()),
sym!(macros::common::;; i()),
sym!(macros::common::..; i()),
sym!(macros::common::_; i()),
sym!(std::tuple::t; i()),
sym!(pattern::match; i()),
sym!(pattern::ref; i()),
sym!(pattern::=>; i()),
sym!(macros::common::+),
sym!(macros::common::*),
sym!(macros::common::,),
sym!(macros::common::;),
sym!(macros::common::..),
sym!(macros::common::_),
sym!(std::tuple::t),
sym!(pattern::match),
sym!(pattern::ref),
sym!(pattern::=>),
]
}
fn lexers() -> Vec<LexerObj> { vec![&MacTreeLexer, &PhLexer] }

View File

@@ -2,7 +2,7 @@ use std::borrow::Cow;
use std::rc::Rc;
use never::Never;
use orchid_base::interner::Tok;
use orchid_base::interner::IStr;
use orchid_base::name::Sym;
use orchid_extension::atom::Atomic;
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant};
@@ -25,8 +25,8 @@ pub struct Macro(pub Rc<MacroData>);
pub struct Rule {
pub pattern: MacTreeSeq,
pub matcher: Matcher,
pub ph_names: Vec<Tok<String>>,
pub body: Tok<String>,
pub ph_names: Vec<IStr>,
pub body: IStr,
}
impl Atomic for Macro {
type Data = ();

View File

@@ -8,7 +8,7 @@ use hashbrown::HashSet;
use orchid_api_derive::Coding;
use orchid_base::error::OrcErrv;
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
use orchid_base::interner::Tok;
use orchid_base::interner::IStr;
use orchid_base::location::Pos;
use orchid_base::name::Sym;
use orchid_base::tl_cache;
@@ -205,7 +205,7 @@ pub async fn mtreev_fmt<'b>(
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Ph {
pub name: Tok<String>,
pub name: IStr,
pub kind: PhKind,
}
impl Display for Ph {

View File

@@ -3,7 +3,7 @@ use std::ops::RangeInclusive;
use futures::FutureExt;
use itertools::chain;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::parse::ParseCtx;
use orchid_base::interner::is;
use orchid_base::tokens::PARENS;
use orchid_base::tree::Paren;
use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable};
@@ -14,7 +14,7 @@ use crate::macros::instantiate_tpl::InstantiateTplCall;
use crate::macros::let_line::parse_tok;
use crate::macros::mactree::{MacTok, MacTree, MacTreeSeq};
#[derive(Default)]
#[derive(Debug, Default)]
pub struct MacTreeLexer;
impl Lexer for MacTreeLexer {
const CHAR_FILTER: &'static [RangeInclusive<char>] = &['\''..='\''];
@@ -54,11 +54,9 @@ impl Lexer for MacTreeLexer {
let tok = MacTok::S(*paren, MacTreeSeq::new(items));
break Ok((tail3, tok.at(ctx.pos_tt(tail, tail3).pos())));
} else if tail2.is_empty() {
return Err(mk_errv(
ctx.i().i("Unclosed block").await,
format!("Expected closing {rp}"),
[ctx.pos_lt(1, tail)],
));
return Err(mk_errv(is("Unclosed block").await, format!("Expected closing {rp}"), [
ctx.pos_lt(1, tail),
]));
}
let (new_tail, new_item) = mac_tree(tail2, args, ctx).boxed_local().await?;
body_tail = new_tail;
@@ -87,7 +85,7 @@ impl Lexer for MacTreeLexer {
Ok((tail3, MacTok::Lambda(param, MacTreeSeq::new(body)).at(ctx.pos_tt(tail, tail3).pos())))
} else {
let (tail2, sub) = ctx.recurse(tail).await?;
let parsed = parse_tok(&sub, ctx).await.expect("Unexpected invalid token");
let parsed = parse_tok(&sub).await.expect("Unexpected invalid token");
Ok((tail2, parsed))
}
}

View File

@@ -8,11 +8,11 @@ use orchid_api::ExprTicket;
use orchid_api_derive::Coding;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::format::fmt;
use orchid_base::interner::is;
use orchid_base::name::Sym;
use orchid_base::sym;
use orchid_extension::atom::{Atomic, TAtom};
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own};
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::{ExecHandle, exec};
use orchid_extension::expr::{Expr, ExprHandle};
@@ -43,7 +43,7 @@ impl MatcherData {
pub fn keys(&self) -> impl Stream<Item = Sym> {
stream(async |mut h| {
for tk in &self.keys {
h.emit(Sym::from_api(*tk, &i()).await).await
h.emit(Sym::from_api(*tk).await).await
}
})
}
@@ -85,7 +85,7 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
},
),
fun(true, "matcher", async |names: HomoTpl<TAtom<SymAtom>>, matcher: Expr| MatcherAtom {
keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0, &i()).await)).await,
keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0).await)).await,
matcher,
}),
build_macro(None, ["match", "match_rule", "_row", "=>"])
@@ -93,7 +93,7 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
async |[value, rules]| {
exec(async move |mut h| {
let rule_lines = h
.exec::<TAtom<Tuple>>(call(sym_ref(sym!(macros::resolve; i())), [
.exec::<TAtom<Tuple>>(call(sym_ref(sym!(macros::resolve)), [
mactree!(macros::common::semi_list "push" rules.clone();).to_gen().await,
]))
.await?;
@@ -105,20 +105,20 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
))
.await?;
let Tpl((matcher, body)) = h
.exec(call(sym_ref(sym!(macros::resolve; i())), [
.exec(call(sym_ref(sym!(macros::resolve)), [
mactree!(pattern::_row "push" own(&line_mac).await ;).to_gen().await,
]))
.await?;
rule_atoms.push((matcher, body));
}
let base_case = lambda(0, [bot(mk_errv(
i().i("No branches match").await,
is("No branches match").await,
"None of the patterns matches this value",
[rules.pos()],
))]);
let match_expr = stream::iter(rule_atoms.into_iter().rev())
.fold(base_case, async |tail, (mat, body)| {
lambda(0, [call(sym_ref(sym!(pattern::match_one; i())), [
lambda(0, [call(sym_ref(sym!(pattern::match_one)), [
mat.to_gen().await,
arg(0),
body.to_gen().await,
@@ -144,14 +144,14 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
async |[pattern, mut value]| {
exec(async move |mut h| -> OrcRes<Tpl<(TAtom<MatcherAtom>, GExpr)>> {
let Ok(pat) = h
.exec::<TAtom<MatcherAtom>>(call(sym_ref(sym!(macros::resolve; i())), [
.exec::<TAtom<MatcherAtom>>(call(sym_ref(sym!(macros::resolve)), [
mactree!(pattern::match_rule "push" pattern.clone();).to_gen().await,
]))
.await
else {
return Err(mk_errv(
i().i("Invalid pattern").await,
format!("Could not parse {} as a match pattern", fmt(&pattern, &i()).await),
is("Invalid pattern").await,
format!("Could not parse {} as a match pattern", fmt(&pattern).await),
[pattern.pos()],
));
};
@@ -169,18 +169,18 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
.rule(mactreev!(pattern::match_rule(pattern::ref "$" name)), [async |[name]| {
let MacTok::Name(name) = name.tok() else {
return Err(mk_errv(
i().i("pattern 'ref' requires a name to bind to").await,
is("pattern 'ref' requires a name to bind to").await,
format!(
"'ref' was interpreted as a binding matcher, \
but it was followed by {} instead of a name",
fmt(&name, &i()).await
fmt(&name).await
),
[name.pos()],
));
};
Ok(MatcherAtom {
keys: vec![name.clone()],
matcher: sym_ref(sym!(pattern::ref_body; i())).to_expr().await,
matcher: sym_ref(sym!(pattern::ref_body)).to_expr().await,
})
}])
.finish(),

View File

@@ -1,10 +1,10 @@
use orchid_api_derive::Coding;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::format::FmtUnit;
use orchid_base::interner::{es, is};
use orchid_base::parse::{name_char, name_start};
use orchid_extension::atom::Atomic;
use orchid_extension::atom_thin::{ThinAtom, ThinVariant};
use orchid_extension::context::i;
use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable};
use orchid_extension::tree::{GenTokTree, x_tok};
@@ -13,7 +13,7 @@ use crate::macros::mactree::{Ph, PhKind};
#[derive(Clone, Coding)]
pub struct PhAtom(orchid_api::TStr, PhKind);
impl PhAtom {
pub async fn to_full(&self) -> Ph { Ph { kind: self.1, name: i().ex(self.0).await } }
pub async fn to_full(&self) -> Ph { Ph { kind: self.1, name: es(self.0).await } }
}
impl Atomic for PhAtom {
type Data = Self;
@@ -21,11 +21,11 @@ impl Atomic for PhAtom {
}
impl ThinAtom for PhAtom {
async fn print(&self) -> FmtUnit {
Ph { name: i().ex(self.0).await, kind: self.1 }.to_string().into()
Ph { name: es(self.0).await, kind: self.1 }.to_string().into()
}
}
#[derive(Default)]
#[derive(Debug, Default)]
pub struct PhLexer;
impl Lexer for PhLexer {
const CHAR_FILTER: &'static [std::ops::RangeInclusive<char>] = &['$'..='$', '.'..='.'];
@@ -52,7 +52,7 @@ impl Lexer for PhLexer {
(prio_num, tail)
} else {
return Err(mk_errv(
i().i("Invalid priority, must be 0-255").await,
is("Invalid priority, must be 0-255").await,
format!("{prio} is not a valid placeholder priority"),
[ctx.pos_lt(prio.len(), tail)],
));
@@ -71,7 +71,7 @@ impl Lexer for PhLexer {
return Err(err_not_applicable().await);
}
};
let ph_atom = PhAtom(i().i::<String>(name).await.to_api(), phkind);
let ph_atom = PhAtom(is(name).await.to_api(), phkind);
Ok((tail, x_tok(ph_atom).await.at(ctx.pos_tt(line, tail))))
}
}

View File

@@ -6,12 +6,13 @@ use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use orchid_base::error::mk_errv;
use orchid_base::format::fmt;
use orchid_base::interner::is;
use orchid_base::location::Pos;
use orchid_base::logging::logger;
use orchid_base::name::{NameLike, Sym, VPath};
use orchid_base::tree::Paren;
use orchid_extension::atom::TAtom;
use orchid_extension::atom_owned::own;
use orchid_extension::context::{ctx, i};
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::{ExecHandle, exec};
use orchid_extension::gen_expr::{GExpr, arg, bot, call, lambda, sym_ref};
@@ -26,17 +27,16 @@ use crate::{MacTok, MacTree};
pub async fn resolve(val: MacTree) -> GExpr {
exec(async move |mut h| {
let ctx = ctx();
// if ctx.logger().is_active() {
writeln!(ctx.logger(), "Macro-resolving {}", fmt(&val, &i()).await);
writeln!(logger(), "Macro-resolving {}", fmt(&val).await);
// }
let root = refl();
let mut macros = HashMap::new();
for n in val.glossary() {
let (foot, body) = n.split_last_seg();
let new_name = VPath::new(body.iter().cloned())
.name_with_suffix(i().i(&format!("__macro__{foot}")).await)
.to_sym(&i())
.name_with_suffix(is(&format!("__macro__{foot}")).await)
.to_sym()
.await;
if let Ok(ReflMemKind::Const) = root.get_by_path(&new_name).await.map(|m| m.kind()) {
let Ok(mac) = h.exec::<TAtom<Macro>>(sym_ref(new_name)).await else { continue };
@@ -67,12 +67,7 @@ pub async fn resolve(val: MacTree) -> GExpr {
}
let mut rctx = ResolveCtx { h, exclusive, priod };
let gex = resolve_one(&mut rctx, Substack::Bottom, &val).await;
writeln!(
ctx.logger(),
"Macro-resolution over {}\nreturned {}",
fmt(&val, &i()).await,
fmt(&gex, &i()).await
);
writeln!(logger(), "Macro-resolution over {}\nreturned {}", fmt(&val).await, fmt(&gex).await);
gex
})
.await
@@ -110,7 +105,7 @@ async fn resolve_one(
MacTok::Lambda(arg, body) => {
let MacTok::Name(name) = &*arg.tok else {
return bot(mk_errv(
i().i("Syntax error after macros").await,
is("Syntax error after macros").await,
"This token ends up as a binding, consider replacing it with a name",
[arg.pos()],
));
@@ -121,8 +116,8 @@ async fn resolve_one(
},
MacTok::S(Paren::Round, body) => resolve_seq(ctx, arg_stk, body.clone(), value.pos()).await,
MacTok::S(..) => bot(mk_errv(
i().i("Leftover [] or {} not matched by macro").await,
format!("{} was not matched by any macro", fmt(value, &i()).await),
is("Leftover [] or {} not matched by macro").await,
format!("{} was not matched by any macro", fmt(value).await),
[value.pos()],
)),
}
@@ -150,7 +145,7 @@ async fn resolve_seq(
) -> GExpr {
if val.items.is_empty() {
return bot(mk_errv(
i().i("Empty sequence").await,
is("Empty sequence").await,
"() or (\\arg ) left after macro execution. \
This is usually caused by an incomplete call to a macro with bad error detection",
[fallback_pos],
@@ -224,7 +219,7 @@ async fn resolve_seq(
Err((lran, rran))
}
});
let mac_conflict_tk = i().i("Macro conflict").await;
let mac_conflict_tk = is("Macro conflict").await;
let error = conflict_sets
.filter(|r| 1 < r.len())
.map(|set| {
@@ -289,5 +284,5 @@ async fn mk_body_call(mac: &Macro, rule: &Rule, state: &MatchState<'_>, pos: Pos
MacTok::S(Paren::Round, MacTreeSeq::new(vec.iter().cloned())).at(Pos::None).to_gen().await,
});
}
call(sym_ref(mac.0.module.suffix([rule.body.clone()], &i()).await), call_args).at(pos.clone())
call(sym_ref(mac.0.module.suffix([rule.body.clone()]).await), call_args).at(pos.clone())
}

View File

@@ -2,17 +2,16 @@ use futures::FutureExt;
use futures::future::join_all;
use itertools::Itertools;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::interner::Tok;
use orchid_base::interner::{IStr, is};
use orchid_base::join_ok;
use orchid_base::side::Side;
use orchid_extension::context::i;
use super::shared::{AnyMatcher, ScalMatcher, VecMatcher};
use super::vec_attrs::vec_attrs;
use crate::macros::mactree::{Ph, PhKind};
use crate::macros::{MacTok, MacTree};
pub type MaxVecSplit<'a> = (&'a [MacTree], (Tok<String>, u8, bool), &'a [MacTree]);
pub type MaxVecSplit<'a> = (&'a [MacTree], (IStr, u8, bool), &'a [MacTree]);
/// Derive the details of the central vectorial and the two sides from a
/// slice of Expr's
@@ -126,7 +125,7 @@ async fn mk_scalar(pattern: &MacTree) -> OrcRes<ScalMatcher> {
MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(&body.items).boxed_local().await?)),
MacTok::Lambda(..) =>
return Err(mk_errv(
i().i("Lambda in matcher").await,
is("Lambda in matcher").await,
"Lambdas can't be matched for, only generated in templates",
[pattern.pos()],
)),
@@ -137,10 +136,11 @@ async fn mk_scalar(pattern: &MacTree) -> OrcRes<ScalMatcher> {
#[cfg(test)]
mod test {
use orchid_base::interner::local_interner::local_interner;
use orchid_base::interner::{is, with_interner};
use orchid_base::location::SrcRange;
use orchid_base::sym;
use orchid_base::tokens::Paren;
use orchid_extension::context::{i, mock_ctx, with_ctx};
use test_executors::spin_on;
use super::mk_any;
@@ -149,27 +149,27 @@ mod test {
#[test]
fn test_scan() {
spin_on(with_ctx(mock_ctx(), async {
let ex = |tok: MacTok| async { tok.at(SrcRange::mock(&i()).await.pos()) };
spin_on(with_interner(local_interner(), async {
let ex = |tok: MacTok| async { tok.at(SrcRange::mock().await.pos()) };
let pattern = vec![
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: i().i("::prefix").await,
name: is("::prefix").await,
}))
.await,
ex(MacTok::Name(sym!(prelude::do; i()))).await,
ex(MacTok::Name(sym!(prelude::do))).await,
ex(MacTok::S(
Paren::Round,
MacTreeSeq::new([
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: i().i("expr").await,
name: is("expr").await,
}))
.await,
ex(MacTok::Name(sym!(prelude::; ; i()))).await,
ex(MacTok::Name(sym!(prelude::;))).await,
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 1, at_least_one: false },
name: i().i("rest").await,
name: is("rest").await,
}))
.await,
]),
@@ -177,7 +177,7 @@ mod test {
.await,
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: i().i("::suffix").await,
name: is("::suffix").await,
}))
.await,
];

View File

@@ -2,8 +2,8 @@ use std::fmt;
use std::rc::Rc;
use orchid_base::error::OrcRes;
use orchid_base::interner::is;
use orchid_base::name::Sym;
use orchid_extension::context::i;
use super::any_match::any_match;
use super::build::mk_any;
@@ -24,12 +24,12 @@ impl Matcher {
let first = pattern.first().expect("Empty pattern is not allowed");
if vec_attrs(first).is_none() {
let pos = first.pos();
pattern.insert(0, MacTok::Ph(Ph { name: i().i("::before").await, kind }).at(pos));
pattern.insert(0, MacTok::Ph(Ph { name: is("::before").await, kind }).at(pos));
}
let last = pattern.last().expect("first returned Some above");
if vec_attrs(last).is_none() {
let pos = last.pos();
pattern.insert(0, MacTok::Ph(Ph { name: i().i("::after").await, kind }).at(pos));
pattern.insert(0, MacTok::Ph(Ph { name: is("::after").await, kind }).at(pos));
}
Ok(Matcher { inner: mk_any(&pattern).await? })
}
@@ -42,7 +42,7 @@ impl Matcher {
) -> Option<(&'a [MacTree], MatchState<'a>, &'a [MacTree])> {
let mut result = any_match(&self.inner, seq, &save_loc)?;
async fn remove_frame<'a>(result: &mut MatchState<'a>, key: &str) -> &'a [MacTree] {
match result.remove(i().i(key).await) {
match result.remove(is(key).await) {
Some(StateEntry::Scalar(_)) => panic!("{key} is defined in the constructor as a Vec"),
Some(StateEntry::Vec(v)) => v,
None => &[],

View File

@@ -3,7 +3,7 @@
use std::fmt;
use itertools::Itertools;
use orchid_base::interner::Tok;
use orchid_base::interner::IStr;
use orchid_base::name::Sym;
use orchid_base::side::Side;
use orchid_base::tokens::{PARENS, Paren};
@@ -11,12 +11,12 @@ use orchid_base::tokens::{PARENS, Paren};
pub enum ScalMatcher {
Name(Sym),
S(Paren, Box<AnyMatcher>),
Placeh { key: Tok<String> },
Placeh { key: IStr },
}
pub enum VecMatcher {
Placeh {
key: Tok<String>,
key: IStr,
nonzero: bool,
},
Scan {
@@ -41,7 +41,7 @@ pub enum VecMatcher {
/// the length of matches on either side.
///
/// Vectorial keys that appear on either side, in priority order
key_order: Vec<Tok<String>>,
key_order: Vec<IStr>,
},
}

View File

@@ -2,7 +2,7 @@
use std::any::Any;
use hashbrown::HashMap;
use orchid_base::interner::Tok;
use orchid_base::interner::IStr;
use orchid_base::join::join_maps;
use orchid_base::location::Pos;
use orchid_base::match_mapping;
@@ -30,11 +30,11 @@ pub enum StateEntry<'a> {
}
#[derive(Clone, Debug)]
pub struct MatchState<'a> {
placeholders: HashMap<Tok<String>, StateEntry<'a>>,
placeholders: HashMap<IStr, StateEntry<'a>>,
name_posv: HashMap<Sym, Vec<Pos>>,
}
impl<'a> MatchState<'a> {
pub fn from_ph(key: Tok<String>, entry: StateEntry<'a>) -> Self {
pub fn from_ph(key: IStr, entry: StateEntry<'a>) -> Self {
Self { placeholders: HashMap::from([(key, entry)]), name_posv: HashMap::new() }
}
pub fn combine(self, s: Self) -> Self {
@@ -45,7 +45,7 @@ impl<'a> MatchState<'a> {
}),
}
}
pub fn ph_len(&self, key: &Tok<String>) -> Option<usize> {
pub fn ph_len(&self, key: &IStr) -> Option<usize> {
match self.placeholders.get(key)? {
StateEntry::Vec(slc) => Some(slc.len()),
_ => None,
@@ -57,10 +57,8 @@ impl<'a> MatchState<'a> {
pub fn names(&self) -> impl Iterator<Item = (Sym, &[Pos])> {
self.name_posv.iter().map(|(sym, vec)| (sym.clone(), &vec[..]))
}
pub fn get(&self, key: &Tok<String>) -> Option<&StateEntry<'a>> { self.placeholders.get(key) }
pub fn remove(&mut self, name: Tok<String>) -> Option<StateEntry<'a>> {
self.placeholders.remove(&name)
}
pub fn get(&self, key: &IStr) -> Option<&StateEntry<'a>> { self.placeholders.get(key) }
pub fn remove(&mut self, name: IStr) -> Option<StateEntry<'a>> { self.placeholders.remove(&name) }
pub fn mk_owned(self) -> OwnedState {
OwnedState {
placeholders: (self.placeholders.into_iter())
@@ -88,10 +86,10 @@ pub enum OwnedEntry {
Scalar(MacTree),
}
pub struct OwnedState {
placeholders: HashMap<Tok<String>, OwnedEntry>,
placeholders: HashMap<IStr, OwnedEntry>,
name_posv: HashMap<Sym, Vec<Pos>>,
}
impl OwnedState {
pub fn get(&self, key: &Tok<String>) -> Option<&OwnedEntry> { self.placeholders.get(key) }
pub fn get(&self, key: &IStr) -> Option<&OwnedEntry> { self.placeholders.get(key) }
pub fn positions(&self, name: &Sym) -> &[Pos] { self.name_posv.get(name).map_or(&[], |v| &v[..]) }
}

View File

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

View File

@@ -3,7 +3,6 @@ use orchid_base::error::OrcRes;
use orchid_base::sym;
use orchid_extension::atom::TAtom;
use orchid_extension::atom_owned::own;
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::exec;
use orchid_extension::expr::Expr;
@@ -39,7 +38,7 @@ pub async fn gen_std_macro_lib() -> Vec<GenMember> {
Ok(MatcherAtom {
keys: sub.keys().collect().await,
matcher: h
.register(call(sym_ref(sym!(std::option::is_some_body; i())), [sub
.register(call(sym_ref(sym!(std::option::is_some_body)), [sub
.to_gen()
.await]))
.await,
@@ -51,7 +50,7 @@ pub async fn gen_std_macro_lib() -> Vec<GenMember> {
exec(async |mut h| {
Ok(MatcherAtom {
keys: vec![],
matcher: h.register(sym_ref(sym!(std::option::is_none_body; i()))).await,
matcher: h.register(sym_ref(sym!(std::option::is_none_body))).await,
})
})
}])
@@ -62,16 +61,16 @@ pub async fn gen_std_macro_lib() -> Vec<GenMember> {
.rule(mactreev!(std::tuple::t [ "...$" elements 0 ]), [|[elements]: [_; _]| {
exec(async move |mut h| {
let tup = h
.exec::<HomoTpl<TAtom<MacTree>>>(call(sym_ref(sym!(macros::resolve; i())), [
.exec::<HomoTpl<TAtom<MacTree>>>(call(sym_ref(sym!(macros::resolve)), [
mactree!((macros::common::comma_list "push" elements ;)).to_gen().await,
]))
.await?;
let val = stream::iter(&tup.0[..])
.fold(sym_ref(sym!(std::tuple::empty; i())), async |head, new| {
call(sym_ref(sym!(std::tuple::cat; i())), [
.fold(sym_ref(sym!(std::tuple::empty)), async |head, new| {
call(sym_ref(sym!(std::tuple::cat)), [
head,
call(sym_ref(sym!(std::tuple::one; i())), [call(
sym_ref(sym!(macros::resolve; i())),
call(sym_ref(sym!(std::tuple::one)), [call(
sym_ref(sym!(macros::resolve)),
[new.clone().to_gen().await],
)]),
])
@@ -102,7 +101,7 @@ pub async fn gen_std_macro_lib() -> Vec<GenMember> {
fn parse_tpl(elements: MacTree, tail_matcher: Option<MacTree>) -> impl Future<Output = GExpr> {
exec(async move |mut h| -> OrcRes<MatcherAtom> {
let tup = h
.exec::<HomoTpl<TAtom<MacTree>>>(call(sym_ref(sym!(macros::resolve; i())), [
.exec::<HomoTpl<TAtom<MacTree>>>(call(sym_ref(sym!(macros::resolve)), [
mactree!((macros::common::comma_list "push" elements ;)).to_gen().await,
]))
.await?;
@@ -110,7 +109,7 @@ fn parse_tpl(elements: MacTree, tail_matcher: Option<MacTree>) -> impl Future<Ou
for mac_a in &tup.0[..] {
let mac = own(mac_a).await;
let sub = h
.exec::<TAtom<MatcherAtom>>(call(sym_ref(sym!(macros::resolve; i())), [
.exec::<TAtom<MatcherAtom>>(call(sym_ref(sym!(macros::resolve)), [
mactree!(pattern::match_rule ("push" mac ;)).to_gen().await,
]))
.await?;
@@ -118,7 +117,7 @@ fn parse_tpl(elements: MacTree, tail_matcher: Option<MacTree>) -> impl Future<Ou
}
let tail_matcher = match tail_matcher {
Some(mac) => Some(
h.exec::<TAtom<MatcherAtom>>(call(sym_ref(sym!(macros::resolve; i())), [
h.exec::<TAtom<MatcherAtom>>(call(sym_ref(sym!(macros::resolve)), [
mactree!(pattern::match_rule "push" mac ;).to_gen().await,
]))
.await?,
@@ -131,7 +130,7 @@ fn parse_tpl(elements: MacTree, tail_matcher: Option<MacTree>) -> impl Future<Ou
.chain(stream::iter(&tail_matcher).flat_map(|mat| mat.keys()))
.collect()
.await,
matcher: call(sym_ref(sym!(std::tuple::matcher_body; i())), [
matcher: call(sym_ref(sym!(std::tuple::matcher_body)), [
HomoTpl(subs).to_gen().await,
OrcOpt(tail_matcher).to_gen().await,
])
@@ -162,8 +161,8 @@ fn tuple_matcher_body(
None => (),
Some(tail_mat) => {
let tail_tpl = stream::iter(&value.0[children.0.len()..])
.fold(sym_ref(sym!(std::tuple::empty; i())), async |prefix, new| {
call(sym_ref(sym!(std::tuple::cat; i())), [prefix, new.clone().to_gen().await])
.fold(sym_ref(sym!(std::tuple::empty)), async |prefix, new| {
call(sym_ref(sym!(std::tuple::cat)), [prefix, new.clone().to_gen().await])
})
.await;
match tail_mat.run_matcher(&mut h, tail_tpl).await? {

View File

@@ -6,10 +6,10 @@ use futures::StreamExt;
use futures::future::LocalBoxFuture;
use itertools::{Itertools, chain};
use never::Never;
use orchid_base::interner::is;
use orchid_base::name::{NameLike, Sym, VPath};
use orchid_extension::atom::{Atomic, TAtom};
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own};
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::gen_expr::{GExpr, sym_ref};
use orchid_extension::tree::{GenMember, MemKind, cnst, lazy};
@@ -101,11 +101,11 @@ impl MacroBuilder {
let Self { own_kws, prio, patterns, body_consts } = self;
let name = own_kws[0];
let main_const = lazy(true, &format!("__macro__{name}"), async move |path| {
let module = (Sym::new(path.split_last_seg().1.iter().cloned(), &i()).await)
let module = (Sym::new(path.split_last_seg().1.iter().cloned()).await)
.expect("Default macro in global root");
MemKind::Const(
Macro(Rc::new(MacroData {
canonical_name: module.suffix([i().i(name).await], &i()).await,
canonical_name: module.suffix([is(name).await]).await,
module,
prio,
rules: stream(async |mut h| {
@@ -121,7 +121,7 @@ impl MacroBuilder {
matcher: Matcher::new(pattern.clone()).await.unwrap(),
pattern,
ph_names: placeholders,
body: i().i(&format!("({name})::{counter}")).await,
body: is(&format!("({name})::{counter}")).await,
})
.await;
}
@@ -136,8 +136,8 @@ impl MacroBuilder {
let kw_consts = own_kws[1..].iter().flat_map(|kw| {
lazy(true, &format!("__macro__{kw}"), async move |path| {
let main_const_name = VPath::new(path.split_last_seg().1.iter().cloned())
.name_with_suffix(i().i(&format!("__macro__{name}")).await)
.to_sym(&i())
.name_with_suffix(is(&format!("__macro__{name}")).await)
.to_sym()
.await;
MemKind::Const(sym_ref(main_const_name))
})
@@ -156,21 +156,21 @@ macro_rules! mactreev_impl {
(@RECUR $ret:ident) => {};
(@RECUR $ret:ident "..$" $name:ident $prio:literal $($tail:tt)*) => {
$ret.push($crate::macros::mactree::MacTok::Ph($crate::macros::mactree::Ph{
name: orchid_extension::context::i().i(stringify!($name)).await,
name: orchid_base::interner::is(stringify!($name)).await,
kind: $crate::macros::mactree::PhKind::Vector{ at_least_one: false, priority: $prio }
}).at(orchid_base::location::Pos::Inherit));
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident "...$" $name:ident $prio:literal $($tail:tt)*) => {
$ret.push($crate::macros::mactree::MacTok::Ph($crate::macros::mactree::Ph{
name: orchid_extension::context::i().i(stringify!($name)).await,
name: orchid_base::interner::is(stringify!($name)).await,
kind: $crate::macros::mactree::PhKind::Vector{ at_least_one: true, priority: $prio }
}).at(orchid_base::location::Pos::Inherit));
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
};
(@RECUR $ret:ident "$" $name:ident $($tail:tt)*) => {
$ret.push($crate::macros::mactree::MacTok::Ph($crate::macros::mactree::Ph{
name: orchid_extension::context::i().i(stringify!(name)).await,
name: orchid_base::interner::is(stringify!(name)).await,
kind: $crate::macros::mactree::PhKind::Scalar
}).at(orchid_base::location::Pos::Inherit));
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
@@ -195,7 +195,7 @@ macro_rules! mactreev_impl {
};
(@RECUR $ret:ident "l" $argh:tt $(:: $arg:tt)+ ($($body:tt)*) $($tail:tt)*) => {
$ret.push(MacTok::Lambda(
MacTok::Name(sym!($argh $(:: $arg)+; orchid_extension::context::i()).await).at(orchid_base::location::Pos::Inherit),
MacTok::Name(sym!($argh $(:: $arg)+).await).at(orchid_base::location::Pos::Inherit),
$crate::macros::utils::mactreev!($($body)*)
).at(orchid_base::location::Pos::Inherit));
$crate::macros::utils::mactreev_impl!(@RECUR $ret $($tail)*);
@@ -207,8 +207,7 @@ macro_rules! mactreev_impl {
$name
);
let sym = orchid_base::name::Sym::parse(
$name,
&orchid_extension::context::i()
$name
).await.expect("Empty string in sym literal in Rust");
$ret.push(
$crate::macros::mactree::MacTok::Name(sym)
@@ -253,7 +252,7 @@ macro_rules! mactreev_impl {
$crate::macros::utils::mactreev_impl!(@NAME_MUNCHER $ret ($($munched)* :: $name) $($tail)*)
};
(@NAME_MUNCHER $ret:ident ($($munched:tt)*) $($tail:tt)*) => {
let sym = orchid_base::sym!($($munched)* ; orchid_extension::context::i());
let sym = orchid_base::sym!($($munched)*);
$ret.push(
$crate::macros::mactree::MacTok::Name(sym)
.at(orchid_base::location::Pos::Inherit)

View File

@@ -1,8 +1,8 @@
use orchid_extension::entrypoint::ExtensionData;
use orchid_extension::entrypoint::ExtensionBuilder;
use orchid_extension::tokio::tokio_main;
use orchid_std::{MacroSystem, StdSystem};
#[tokio::main(flavor = "current_thread")]
pub async fn main() {
tokio_main(ExtensionData::new("orchid-std::main", &[&StdSystem, &MacroSystem])).await
tokio_main(ExtensionBuilder::new("orchid-std::main").system(StdSystem).system(MacroSystem)).await
}

View File

@@ -6,7 +6,6 @@ use orchid_base::name::Sym;
use orchid_base::number::Numeric;
use orchid_extension::atom::{AtomFactory, Atomic, AtomicFeatures, Supports, TAtom, ToAtom};
use orchid_extension::atom_thin::{ThinAtom, ThinVariant};
use orchid_extension::context::i;
use orchid_extension::conv::TryFromExpr;
use orchid_extension::expr::Expr;
use ordered_float::NotNan;
@@ -31,7 +30,7 @@ impl TryFromExpr for Int {
}
impl Supports<GetTagIdMethod> for Int {
async fn handle(&self, _: GetTagIdMethod) -> <GetTagIdMethod as Request>::Response {
Sym::parse("std::number::Int", &i()).await.unwrap().to_api()
Sym::parse("std::number::Int").await.unwrap().to_api()
}
}
impl Supports<ToStringMethod> for Int {

View File

@@ -3,13 +3,12 @@ use std::ops::RangeInclusive;
use orchid_base::error::OrcRes;
use orchid_base::number::{num_to_errv, parse_num};
use orchid_extension::atom::ToAtom;
use orchid_extension::context::i;
use orchid_extension::lexer::{LexContext, Lexer};
use orchid_extension::tree::{GenTokTree, x_tok};
use super::num_atom::Num;
#[derive(Default)]
#[derive(Debug, Default)]
pub struct NumLexer;
impl Lexer for NumLexer {
const CHAR_FILTER: &'static [RangeInclusive<char>] = &['0'..='9'];
@@ -18,7 +17,7 @@ impl Lexer for NumLexer {
let (chars, tail) = all.split_at(ends_at.unwrap_or(all.len()));
let fac = match parse_num(chars) {
Ok(numeric) => Num(numeric).to_atom_factory(),
Err(e) => return Err(num_to_errv(e, lxcx.pos(all), lxcx.src(), &i()).await),
Err(e) => return Err(num_to_errv(e, lxcx.pos(all), lxcx.src()).await),
};
Ok((tail, x_tok(fac).await.at(lxcx.pos_lt(chars.len(), tail))))
}

View File

@@ -4,10 +4,10 @@ use std::pin::Pin;
use futures::AsyncWrite;
use orchid_api_traits::Encode;
use orchid_base::error::mk_errv;
use orchid_base::interner::is;
use orchid_base::sym;
use orchid_extension::atom::{Atomic, ForeignAtom, TAtom};
use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant};
use orchid_extension::context::i;
use orchid_extension::conv::{ToExpr, TryFromExpr};
use orchid_extension::expr::{Expr, ExprHandle};
use orchid_extension::gen_expr::{call, sym_ref};
@@ -30,7 +30,7 @@ impl OwnedAtom for OptAtom {
Self(ctx.read::<bool>().await.then(|| refs.into_iter().next().unwrap()))
}
async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs {
self.0.is_some().encode(write).await;
self.0.is_some().encode(write).await.unwrap();
self.0.iter().cloned().collect()
}
}
@@ -50,9 +50,9 @@ impl<T: TryFromExpr> TryFromExpr for OrcOpt<T> {
impl<T: ToExpr + 'static> ToExpr for OrcOpt<T> {
async fn to_gen(self) -> orchid_extension::gen_expr::GExpr {
if let Some(val) = self.0 {
call(sym_ref(sym!(std::option::some; i())), [val.to_gen().await])
call(sym_ref(sym!(std::option::some)), [val.to_gen().await])
} else {
sym_ref(sym!(std::option::none; i()))
sym_ref(sym!(std::option::none))
}
}
}
@@ -64,11 +64,10 @@ pub fn gen_option_lib() -> Vec<GenMember> {
fun(true, "expect", async |opt: ForeignAtom, msg: OrcString| {
match OrcOpt::try_from_expr(opt.clone().ex()).await? {
OrcOpt(Some(ex)) => Ok::<Expr, _>(ex),
OrcOpt(None) => Err(mk_errv(
i().i("Unwrapped std::option::none").await,
msg.get_string().await.as_str(),
[opt.pos()],
)),
OrcOpt(None) =>
Err(mk_errv(is("Unwrapped std::option::none").await, msg.get_string().await.as_str(), [
opt.pos(),
])),
}
}),
])

View File

@@ -1,9 +1,9 @@
use itertools::{Itertools, chain};
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::interner::Tok;
use orchid_base::interner::{IStr, is};
use orchid_base::name::Sym;
use orchid_base::parse::{
Import, ParseCtx, Parsed, Snippet, expect_tok, line_items, parse_multiname, token_errv,
Import, Parsed, Snippet, expect_tok, line_items, parse_multiname, token_errv,
};
use orchid_base::tree::{Paren, Token};
use orchid_extension::parser::{
@@ -11,57 +11,54 @@ use orchid_extension::parser::{
};
pub async fn parse_impls(
ctx: &ParsCtx<'_>,
_: &ParsCtx<'_>,
lines: &mut Vec<ParsedLine>,
impls: &mut Vec<(Sym, Tok<String>)>,
impls: &mut Vec<(Sym, IStr)>,
body_tt: &PTokTree,
) -> OrcRes<()> {
let i = ctx.i().clone();
let body = match &body_tt.tok {
Token::S(Paren::Round, body) => line_items(ctx, Snippet::new(body_tt, body)).await,
Token::S(Paren::Round, body) => line_items(Snippet::new(body_tt, body)).await,
Token::S(ptyp, _) =>
return Err(mk_errv(
i.i("Incorrect paren type").await,
is("Incorrect paren type").await,
format!("Expected () block, found {ptyp}"),
[body_tt.sr().pos()],
)),
_ =>
return Err(
token_errv(ctx, body_tt, "Expected body", |s| {
format!("Expected (impl ...) block, found {s}")
})
.await,
token_errv(body_tt, "Expected body", |s| format!("Expected (impl ...) block, found {s}"))
.await,
),
};
for Parsed { tail: line, output: comments } in body {
if let Ok(Parsed { tail, .. }) = expect_tok(ctx, line, i.i("impl").await).await {
let Parsed { tail, output: name_tt } = parse_multiname(ctx, tail).await?;
if let Ok(Parsed { tail, .. }) = expect_tok(line, is("impl").await).await {
let Parsed { tail, output: name_tt } = parse_multiname(tail).await?;
let (name, name_sr) = match name_tt.into_iter().at_most_one() {
Ok(None) => panic!("multiname is always at least one name"),
Ok(Some(ref n @ Import { name: Some(_), ref sr, .. })) =>
(n.clone().mspath().to_sym(&i).await, sr.clone()),
(n.clone().mspath().to_sym().await, sr.clone()),
Ok(Some(Import { name: None, sr, .. })) =>
return Err(mk_errv(
i.i("impl line with globstar").await,
is("impl line with globstar").await,
"::* is not permitted in a protocol impl",
[sr.pos()],
)),
Err(e) =>
return Err(mk_errv(
i.i("Impl line with multiple protocol names").await,
is("Impl line with multiple protocol names").await,
"::() is not permitted in a protocol impl",
e.map(|i| i.sr.pos()),
)),
};
let Parsed { tail, .. } = expect_tok(ctx, tail, i.i("as").await).await?;
let cnst_name = i.i(&format!("{}{}", lines.len(), name.iter().join("__"))).await;
let Parsed { tail, .. } = expect_tok(tail, is("as").await).await?;
let cnst_name = is(&format!("{}{}", lines.len(), name.iter().join("__"))).await;
lines.push(ParsedLine {
comments,
sr: line.sr(),
kind: ParsedLineKind::Rec(Vec::from_iter(chain![
[Token::Name(i.i("let").await).at(line.sr())],
[Token::Name(is("let").await).at(line.sr())],
[Token::Name(cnst_name.clone()).at(name_sr)],
[Token::Name(i.i("=").await).at(line.sr())],
[Token::Name(is("=").await).at(line.sr())],
tail.iter().cloned().map(p_tree2gen),
])),
});

View File

@@ -2,10 +2,10 @@ use std::rc::Rc;
use hashbrown::HashMap;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::interner::is;
use orchid_base::parse::{Comment, Parsed, expect_end, try_pop_no_fluff};
use orchid_base::sym;
use orchid_base::tree::Token;
use orchid_extension::context::i;
use orchid_extension::coroutine_exec::exec;
use orchid_extension::gen_expr::{call, sym_ref};
use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser};
@@ -23,11 +23,11 @@ impl Parser for AsProtoParser {
cmts: Vec<Comment>,
line: PSnippet<'a>,
) -> OrcRes<Vec<ParsedLine>> {
let Parsed { output: body_tt, tail } = try_pop_no_fluff(&pcx, line).await?;
expect_end(&pcx, tail).await?;
let Parsed { output: body_tt, tail } = try_pop_no_fluff(line).await?;
expect_end(tail).await?;
if exported {
return Err(mk_errv(
i().i("Exported internal line").await,
is("Exported internal line").await,
"as_proto cannot be exported, the type shares the enclosing module's visibility",
[line.sr().pos()],
));
@@ -36,20 +36,20 @@ impl Parser for AsProtoParser {
let mut impls = Vec::new();
parse_impls(&pcx, &mut lines, &mut impls, body_tt).await?;
let id = pcx.module();
let proto_tag_name = i().i("__protocol_tag__").await;
let proto_tag_path = id.suffix([proto_tag_name.clone()], &i()).await;
let proto_tag_name = is("__protocol_tag__").await;
let proto_tag_path = id.suffix([proto_tag_name.clone()]).await;
lines.push(ParsedLine::cnst(&line.sr(), &cmts, true, proto_tag_name, async |_ccx| {
exec(async move |mut h| {
let mut new_impls = HashMap::new();
for (k, v) in impls {
new_impls.insert(k.clone(), h.register(sym_ref(id.suffix([v], &i()).await)).await);
new_impls.insert(k.clone(), h.register(sym_ref(id.suffix([v]).await)).await);
}
Tag { id, impls: Rc::new(new_impls) }
})
.await
}));
lines.push(ParsedLine::cnst(&line.sr(), [], false, i().i("resolve").await, async move |_| {
call(sym_ref(sym!(std::protocol::resolve; i())), [sym_ref(proto_tag_path)])
lines.push(ParsedLine::cnst(&line.sr(), [], false, is("resolve").await, async move |_| {
call(sym_ref(sym!(std::protocol::resolve)), [sym_ref(proto_tag_path)])
}));
Ok(lines)
}
@@ -65,9 +65,9 @@ impl Parser for ProtoParser {
cmts: Vec<Comment>,
line: PSnippet<'a>,
) -> OrcRes<Vec<ParsedLine>> {
let Parsed { output: name_tt, tail } = try_pop_no_fluff(&ctx, line).await?;
let Parsed { output: name_tt, tail } = try_pop_no_fluff(line).await?;
let Token::Name(name) = &name_tt.tok else {
return Err(mk_errv(i().i("missing name for type").await, "A type needs a name", [name_tt
return Err(mk_errv(is("missing name for type").await, "A type needs a name", [name_tt
.sr()
.pos()]));
};

View File

@@ -2,10 +2,10 @@ use std::rc::Rc;
use hashbrown::HashMap;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::interner::is;
use orchid_base::parse::{Comment, Parsed, expect_end, try_pop_no_fluff};
use orchid_base::sym;
use orchid_base::tree::Token;
use orchid_extension::context::i;
use orchid_extension::coroutine_exec::exec;
use orchid_extension::gen_expr::{call, sym_ref};
use orchid_extension::parser::{PSnippet, ParsCtx, ParsedLine, Parser};
@@ -23,11 +23,11 @@ impl Parser for AsTypeParser {
cmts: Vec<Comment>,
line: PSnippet<'a>,
) -> OrcRes<Vec<ParsedLine>> {
let Parsed { output: body_tt, tail } = try_pop_no_fluff(&ctx, line).await?;
expect_end(&ctx, tail).await?;
let Parsed { output: body_tt, tail } = try_pop_no_fluff(line).await?;
expect_end(tail).await?;
if exported {
return Err(mk_errv(
i().i("Exported internal line").await,
is("Exported internal line").await,
"as_type cannot be exported, the type shares the enclosing module's visibility",
[line.sr().pos()],
));
@@ -36,25 +36,25 @@ impl Parser for AsTypeParser {
let mut impls = Vec::new();
parse_impls(&ctx, &mut lines, &mut impls, body_tt).await?;
let id = ctx.module();
let type_tag_name = i().i("__type_tag__").await;
let type_tag_path = id.suffix([type_tag_name.clone()], &i()).await;
let type_tag_name = is("__type_tag__").await;
let type_tag_path = id.suffix([type_tag_name.clone()]).await;
lines.push(ParsedLine::cnst(&line.sr(), &cmts, true, type_tag_name, async |_ccx| {
exec(async move |mut h| {
let mut new_impls = HashMap::new();
for (k, v) in impls {
new_impls.insert(k.clone(), h.register(sym_ref(id.suffix([v], &i()).await)).await);
new_impls.insert(k.clone(), h.register(sym_ref(id.suffix([v]).await)).await);
}
Tag { id, impls: Rc::new(new_impls) }
})
.await
}));
let type_tag_path_1 = type_tag_path.clone();
lines.push(ParsedLine::cnst(&line.sr(), [], false, i().i("wrap").await, async move |_ccx| {
call(sym_ref(sym!(std::protocol::wrap; i())), [sym_ref(type_tag_path_1)])
lines.push(ParsedLine::cnst(&line.sr(), [], false, is("wrap").await, async move |_ccx| {
call(sym_ref(sym!(std::protocol::wrap)), [sym_ref(type_tag_path_1)])
}));
let type_tag_path_1 = type_tag_path.clone();
lines.push(ParsedLine::cnst(&line.sr(), [], false, i().i("unwrap").await, async move |_ccx| {
call(sym_ref(sym!(std::protocol::unwrap; i())), [sym_ref(type_tag_path_1)])
lines.push(ParsedLine::cnst(&line.sr(), [], false, is("unwrap").await, async move |_ccx| {
call(sym_ref(sym!(std::protocol::unwrap)), [sym_ref(type_tag_path_1)])
}));
Ok(lines)
}
@@ -70,9 +70,9 @@ impl Parser for TypeParser {
cmts: Vec<Comment>,
line: PSnippet<'a>,
) -> OrcRes<Vec<ParsedLine>> {
let Parsed { output: name_tt, tail } = try_pop_no_fluff(&ctx, line).await?;
let Parsed { output: name_tt, tail } = try_pop_no_fluff(line).await?;
let Token::Name(name) = &name_tt.tok else {
return Err(mk_errv(i().i("missing name for type").await, "A type needs a name", [name_tt
return Err(mk_errv(is("missing name for type").await, "A type needs a name", [name_tt
.sr()
.pos()]));
};

View File

@@ -7,10 +7,10 @@ use orchid_api_derive::Coding;
use orchid_api_traits::Request;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::format::fmt;
use orchid_base::interner::is;
use orchid_base::name::Sym;
use orchid_extension::atom::{AtomMethod, Atomic, ForeignAtom, MethodSetBuilder, Supports, TAtom};
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own};
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::expr::Expr;
use orchid_extension::gen_expr::call;
@@ -34,7 +34,7 @@ impl OwnedAtom for Tag {
}
impl Supports<GetImplMethod> for Tag {
async fn handle(&self, req: GetImplMethod) -> <GetImplMethod as Request>::Response {
self.impls.get(&Sym::from_api(req.0, &i()).await).map(|expr| expr.handle().ticket())
self.impls.get(&Sym::from_api(req.0).await).map(|expr| expr.handle().ticket())
}
}
#[derive(Clone, Debug, Coding)]
@@ -75,13 +75,13 @@ impl Supports<GetImplMethod> for Tagged {
pub async fn get_impl(receiver: ForeignAtom, proto: ForeignAtom) -> OrcRes<Expr> {
let Some(proto_id) = proto.request(GetTagIdMethod).await else {
return Err(mk_errv(i().i("Not a protocol").await, "Protocol does not have a tag ID", [
return Err(mk_errv(is("Not a protocol").await, "Protocol does not have a tag ID", [
proto.pos()
]));
};
let Some(impl_val_opt) = receiver.request(GetImplMethod(proto_id)).await else {
return Err(mk_errv(
i().i("Receiver not tagged").await,
is("Receiver not tagged").await,
"The receiver does not have a type tag",
[receiver.pos()],
));
@@ -91,14 +91,14 @@ pub async fn get_impl(receiver: ForeignAtom, proto: ForeignAtom) -> OrcRes<Expr>
}
let Some(type_id) = receiver.request(GetTagIdMethod).await else {
return Err(mk_errv(
i().i("Incorrect protocols implementation in extension").await,
is("Incorrect protocols implementation in extension").await,
"Atom provides an impl table but no tag ID",
[receiver.pos()],
));
};
let Some(impl_val_opt) = proto.request(GetImplMethod(type_id)).await else {
return Err(mk_errv(
i().i("Incorrect protocols implementation in extension").await,
is("Incorrect protocols implementation in extension").await,
"Proto table atom provides a tag ID but no impl table",
[receiver.pos()],
));
@@ -107,7 +107,7 @@ pub async fn get_impl(receiver: ForeignAtom, proto: ForeignAtom) -> OrcRes<Expr>
return Ok(Expr::deserialize(impl_val).await);
}
return Err(mk_errv(
i().i("Implementation not found").await,
is("Implementation not found").await,
"This protocol is not implemented for this receiver",
[receiver.pos(), proto.pos()],
));
@@ -126,13 +126,8 @@ pub fn gen_protocol_lib() -> Vec<GenMember> {
Ok(own_val.value.to_gen().await)
} else {
Err(mk_errv(
i().i("Type mismatch").await,
format!(
"{} has type {}, expected {}",
fmt(&value, &i()).await,
own_val.tag.id,
own_tag.id
),
is("Type mismatch").await,
format!("{} has type {}, expected {}", fmt(&value).await, own_val.tag.id, own_tag.id),
[value.pos()],
))
}

View File

@@ -6,16 +6,15 @@ use futures::AsyncWrite;
use futures::future::join_all;
use hashbrown::HashMap;
use orchid_api_traits::Encode;
use orchid_base::interner::Tok;
use orchid_base::interner::{IStr, es};
use orchid_extension::atom::Atomic;
use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant};
use orchid_extension::context::i;
use orchid_extension::expr::Expr;
use crate::api;
#[derive(Clone)]
pub struct Record(pub Rc<HashMap<Tok<String>, Expr>>);
pub struct Record(pub Rc<HashMap<IStr, Expr>>);
impl Atomic for Record {
type Data = ();
type Variant = OwnedVariant;
@@ -25,13 +24,12 @@ impl OwnedAtom for Record {
async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs {
let (keys, values) =
self.0.iter().map(|(k, v)| (k.to_api(), v.clone())).unzip::<_, _, Vec<_>, Vec<_>>();
keys.encode(write).await;
keys.encode(write).await.unwrap();
values
}
async fn deserialize(mut dctx: impl DeserializeCtx, refs: Self::Refs) -> Self {
let keys =
join_all(dctx.decode::<Vec<api::TStr>>().await.iter().map(|t| async { i().ex(*t).await }))
.await;
join_all(dctx.decode::<Vec<api::TStr>>().await.iter().map(|t| async { es(*t).await })).await;
Record(Rc::new(keys.into_iter().zip(refs).collect()))
}

View File

@@ -4,10 +4,10 @@ use orchid_api::TStrv;
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use orchid_base::error::mk_errv;
use orchid_base::interner::{es, is};
use orchid_base::name::{NameLike, Sym};
use orchid_extension::atom::{Atomic, Supports, TAtom};
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant, own};
use orchid_extension::context::i;
use orchid_extension::expr::{Expr, ExprHandle};
use orchid_extension::system::dep_req;
use orchid_extension::tree::{GenMember, fun, prefix};
@@ -51,10 +51,10 @@ pub async fn sym_expr(sym: Sym) -> Expr {
pub async fn gen_sym_lib() -> Vec<GenMember> {
prefix("std::refl::sym", [
fun(true, "from_str", async move |str: TAtom<IntStrAtom>| {
match Sym::parse(&i().ex(*str).await, &i()).await {
match Sym::parse(&es(*str).await).await {
Ok(sym) => Ok(SymAtom(sym)),
Err(_) => Err(mk_errv(
i().i("Cannot parse sym from empty string").await,
is("Cannot parse sym from empty string").await,
"Empty string passed to std::refl::sym::from_str",
[str.pos()],
)),

View File

@@ -3,12 +3,10 @@ use std::rc::Rc;
use futures::future::join_all;
use orchid_api_derive::{Coding, Hierarchy};
use orchid_base::name::Sym;
use orchid_base::reqnot::Receipt;
use orchid_base::reqnot::{Receipt, ReqHandle, ReqHandleExt};
use orchid_base::sym;
use orchid_extension::atom::{AtomDynfo, AtomicFeatures};
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::entrypoint::ExtReq;
use orchid_extension::expr::Expr;
use orchid_extension::lexer::LexerObj;
use orchid_extension::parser::ParserObj;
@@ -39,14 +37,14 @@ pub enum StdReq {
CreateSymAtom(CreateSymAtom),
}
#[derive(Default)]
#[derive(Debug, Default)]
pub struct StdSystem;
impl SystemCtor for StdSystem {
type Deps = ();
type Instance = Self;
const NAME: &'static str = "orchid::std";
const VERSION: f64 = 0.00_01;
fn inst(_: ()) -> Self::Instance { Self }
fn inst(&self, _: ()) -> Self::Instance { Self }
}
impl SystemCard for StdSystem {
type Ctor = Self;
@@ -68,16 +66,16 @@ impl SystemCard for StdSystem {
}
}
impl System for StdSystem {
async fn request(xreq: ExtReq<'_>, req: Self::Req) -> Receipt<'_> {
async fn request<'a>(xreq: Box<dyn ReqHandle<'a> + 'a>, req: Self::Req) -> Receipt<'a> {
match req {
StdReq::CreateTuple(ref req @ CreateTuple(ref items)) => {
let tpl = Tuple(Rc::new(join_all(items.iter().copied().map(Expr::deserialize)).await));
let tk = tpl.to_expr().await.serialize().await;
xreq.handle(req, &tk).await
xreq.reply(req, &tk).await.unwrap()
},
StdReq::CreateSymAtom(ref req @ CreateSymAtom(sym_tok)) => {
let sym_atom = SymAtom(Sym::from_api(sym_tok, &i()).await);
xreq.handle(req, &sym_atom.to_expr().await.serialize().await).await
let sym_atom = SymAtom(Sym::from_api(sym_tok).await);
xreq.reply(req, &sym_atom.to_expr().await.serialize().await).await.unwrap()
},
}
}
@@ -94,7 +92,5 @@ impl System for StdSystem {
gen_sym_lib().await,
])
}
async fn prelude() -> Vec<Sym> {
vec![sym!(std; i()), sym!(std::tuple; i()), sym!(std::option; i())]
}
async fn prelude() -> Vec<Sym> { vec![sym!(std), sym!(std::tuple), sym!(std::option)] }
}

View File

@@ -8,10 +8,9 @@ use orchid_api_derive::Coding;
use orchid_api_traits::{Encode, Request};
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::format::{FmtCtx, FmtUnit};
use orchid_base::interner::Tok;
use orchid_base::interner::{IStr, es, is};
use orchid_extension::atom::{AtomMethod, Atomic, MethodSetBuilder, Supports, TAtom};
use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant};
use orchid_extension::context::i;
use orchid_extension::conv::TryFromExpr;
use orchid_extension::expr::Expr;
@@ -52,7 +51,7 @@ impl OwnedAtom for StrAtom {
type Refs = ();
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
async fn serialize(&self, sink: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs {
self.deref().encode(sink).await
self.deref().encode(sink).await.unwrap()
}
async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
format!("{:?}", &*self.0).into()
@@ -63,36 +62,36 @@ impl OwnedAtom for StrAtom {
}
#[derive(Debug, Clone)]
pub struct IntStrAtom(pub(crate) Tok<String>);
pub struct IntStrAtom(pub(crate) IStr);
impl Atomic for IntStrAtom {
type Variant = OwnedVariant;
type Data = orchid_api::TStr;
}
impl From<Tok<String>> for IntStrAtom {
fn from(value: Tok<String>) -> Self { Self(value) }
impl From<IStr> for IntStrAtom {
fn from(value: IStr) -> Self { Self(value) }
}
impl OwnedAtom for IntStrAtom {
type Refs = ();
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.0.to_api()) }
async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
format!("{:?}i", *self.0).into()
format!("{:?}i", &*self.0).into()
}
async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) {
self.0.encode(write).await
self.0.encode(write).await.unwrap()
}
async fn deserialize(mut dctx: impl DeserializeCtx, _: ()) -> Self {
let s = dctx.decode::<String>().await;
Self(i().i(&s).await)
Self(is(&s).await)
}
}
impl TryFromExpr for IntStrAtom {
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
Ok(IntStrAtom(i().ex(TAtom::<IntStrAtom>::try_from_expr(expr).await?.value).await))
Ok(IntStrAtom(es(TAtom::<IntStrAtom>::try_from_expr(expr).await?.value).await))
}
}
impl Supports<ToStringMethod> for IntStrAtom {
async fn handle(&self, _: ToStringMethod) -> <ToStringMethod as Request>::Response {
self.0.as_str().to_string()
self.0.to_string()
}
}
@@ -109,7 +108,7 @@ pub enum OrcStringKind {
impl OrcString {
pub async fn get_string(&self) -> Rc<String> {
match &self.kind {
OrcStringKind::Int(tok) => i().ex(**tok).await.rc(),
OrcStringKind::Int(tok) => es(**tok).await.rc(),
OrcStringKind::Val(atom) => atom.request(StringGetVal).await,
}
}
@@ -122,7 +121,7 @@ impl TryFromExpr for OrcString {
}
match TAtom::<IntStrAtom>::try_from_expr(expr).await {
Ok(t) => Ok(OrcString { kind: OrcStringKind::Int(t) }),
Err(e) => Err(mk_errv(i().i("A string was expected").await, "", e.pos_iter())),
Err(e) => Err(mk_errv(is("A string was expected").await, "", e.pos_iter())),
}
}
}

View File

@@ -1,12 +1,10 @@
use itertools::Itertools;
use orchid_base::error::{OrcErr, OrcErrv, OrcRes, mk_errv};
use orchid_base::interner::Interner;
use orchid_base::interner::is;
use orchid_base::location::SrcRange;
use orchid_base::name::Sym;
use orchid_base::parse::ParseCtx;
use orchid_base::sym;
use orchid_base::tree::{Paren, wrap_tokv};
use orchid_extension::context::i;
use orchid_extension::gen_expr::sym_ref;
use orchid_extension::lexer::{LexContext, Lexer, err_not_applicable};
use orchid_extension::parser::p_tree2gen;
@@ -36,10 +34,10 @@ struct StringError {
impl StringError {
/// Convert into project error for reporting
pub async fn into_proj(self, path: &Sym, pos: u32, i: &Interner) -> OrcErrv {
pub async fn into_proj(self, path: &Sym, pos: u32) -> OrcErrv {
let start = pos + self.pos;
mk_errv(
i.i("Failed to parse string").await,
is("Failed to parse string").await,
match self.kind {
StringErrorKind::NotHex => "Expected a hex digit",
StringErrorKind::BadCodePoint => "The specified number is not a Unicode code point",
@@ -95,7 +93,7 @@ fn parse_string(str: &str) -> Result<String, StringError> {
Ok(target)
}
#[derive(Default)]
#[derive(Debug, Default)]
pub struct StringLexer;
impl Lexer for StringLexer {
const CHAR_FILTER: &'static [std::ops::RangeInclusive<char>] = &['"'..='"', '`'..='`'];
@@ -114,18 +112,16 @@ impl Lexer for StringLexer {
) -> GenTokTree {
let str_val_res = parse_string(&str.split_off(0));
if let Err(e) = &str_val_res {
err.extend(e.clone().into_proj(ctx.src(), ctx.pos(tail) - str.len() as u32, ctx.i()).await);
err.extend(e.clone().into_proj(ctx.src(), ctx.pos(tail) - str.len() as u32).await);
}
let str_val = str_val_res.unwrap_or_default();
x_tok(IntStrAtom::from(ctx.i().i(&*str_val).await))
.await
.at(ctx.pos_lt(str.len() as u32, tail)) as GenTokTree
x_tok(IntStrAtom::from(is(&str_val).await)).await.at(ctx.pos_lt(str.len() as u32, tail))
as GenTokTree
}
let add_frag = |prev: Option<GenTokTree>, new: GenTokTree| async {
let Some(prev) = prev else { return new };
let concat_fn = ref_tok(sym!(std::string::concat; lctx.i()))
.await
.at(SrcRange::zw(prev.sr.path(), prev.sr.start()));
let concat_fn =
ref_tok(sym!(std::string::concat)).await.at(SrcRange::zw(prev.sr.path(), prev.sr.start()));
wrap_tokv([concat_fn, prev, new])
};
loop {
@@ -139,7 +135,7 @@ impl Lexer for StringLexer {
let (new_tail, tree) = lctx.recurse(rest).await?;
tail = new_tail;
// wrap the received token in a call to to_str
let to_str = sym_ref(sym!(std::string::to_str; i()));
let to_str = sym_ref(sym!(std::string::to_str));
let sr = tree.sr();
let inj_to_str_tok = GenTok::NewExpr(to_str).at(sr.map_range(|_| sr.start()..sr.start()));
let to_str_call = GenTok::S(Paren::Round, vec![inj_to_str_tok, p_tree2gen(tree)]).at(sr);
@@ -154,11 +150,9 @@ impl Lexer for StringLexer {
tail = ch.as_str();
} else {
let range = lctx.pos(all)..lctx.pos("");
return Err(mk_errv(
lctx.i().i("No string end").await,
"String never terminated with \"",
[SrcRange::new(range.clone(), lctx.src())],
));
return Err(mk_errv(is("No string end").await, "String never terminated with \"", [
SrcRange::new(range.clone(), lctx.src()),
]));
}
}
}

View File

@@ -3,7 +3,6 @@ use std::rc::Rc;
use orchid_base::format::fmt;
use orchid_base::sym;
use orchid_extension::atom::ForeignAtom;
use orchid_extension::context::i;
use orchid_extension::conv::ToExpr;
use orchid_extension::coroutine_exec::exec;
use orchid_extension::expr::Expr;
@@ -35,13 +34,13 @@ pub fn gen_str_lib() -> Vec<GenMember> {
if let Some(str) = atom.request(ToStringMethod).await {
return StrAtom::new(Rc::new(str)).to_gen().await;
}
let proto_ref = sym_ref(sym!(std::string::to_string::__protocol_tag__; i()));
let proto_ref = sym_ref(sym!(std::string::to_string::__protocol_tag__));
let proto = h.exec(proto_ref).await.expect("This protocol is defined in this system");
if let Ok(cb) = get_impl(atom.clone(), proto).await {
return call(cb.to_gen().await, [atom.to_gen().await]).to_gen().await;
}
}
return StrAtom::new(Rc::new(fmt(&input, &i()).await)).to_gen().await;
return StrAtom::new(Rc::new(fmt(&input).await)).to_gen().await;
})
.await
}),
@@ -50,7 +49,7 @@ pub fn gen_str_lib() -> Vec<GenMember> {
cnst(true, "__type_tag__", AsStrTag),
fun(true, "resolve", async |atom: ForeignAtom| {
exec(async |mut h| {
let proto = h.exec(sym_ref(sym!(std::string::to_string; i()))).await?;
let proto = h.exec(sym_ref(sym!(std::string::to_string))).await?;
Ok(call(get_impl(atom.clone(), proto).await?.to_gen().await, [atom.to_gen().await]))
})
.await

View File

@@ -3,7 +3,6 @@ use orchid_api_traits::Request;
use orchid_base::name::Sym;
use orchid_extension::atom::{AtomMethod, Atomic, MethodSetBuilder, Supports};
use orchid_extension::atom_thin::{ThinAtom, ThinVariant};
use orchid_extension::context::i;
use crate::std::protocol::types::{GetImplMethod, GetTagIdMethod};
@@ -19,7 +18,7 @@ impl Atomic for AsStrTag {
impl ThinAtom for AsStrTag {}
impl Supports<GetTagIdMethod> for AsStrTag {
async fn handle(&self, _: GetTagIdMethod) -> <GetTagIdMethod as Request>::Response {
Sym::parse("std::string::to_string", &i()).await.unwrap().to_api()
Sym::parse("std::string::to_string").await.unwrap().to_api()
}
}
impl Supports<GetImplMethod> for AsStrTag {

View File

@@ -11,9 +11,9 @@ use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
use orchid_base::interner::is;
use orchid_extension::atom::{Atomic, TAtom};
use orchid_extension::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant, own};
use orchid_extension::context::i;
use orchid_extension::conv::{ToExpr, TryFromExpr};
use orchid_extension::expr::{Expr, ExprHandle};
use orchid_extension::gen_expr::GExpr;
@@ -95,7 +95,7 @@ pub fn gen_tuple_lib() -> Vec<GenMember> {
return Ok(val.clone());
}
return Err(mk_errv(
i().i("Tuple index out of bounds").await,
is("Tuple index out of bounds").await,
format!("{} is out of bounds for Tuple{}", idx.0, tup.len()),
[idx.pos()],
));
@@ -109,7 +109,7 @@ pub fn gen_tuple_lib() -> Vec<GenMember> {
}
}
return Err(mk_errv(
i().i("Tuple index out of bounds").await,
is("Tuple index out of bounds").await,
format!("{} is out of bounds for Tuple{}", idx.0, tup.len()),
[idx.pos()],
));
@@ -145,7 +145,7 @@ pub struct Tpl<T>(pub T);
mod tpl_impls {
use itertools::Itertools;
use orchid_base::error::{OrcRes, mk_errv};
use orchid_extension::context::i;
use orchid_base::interner::is;
use orchid_extension::conv::{ToExpr, TryFromExpr};
use orchid_extension::expr::Expr;
use orchid_extension::gen_expr::GExpr;
@@ -160,7 +160,7 @@ mod tpl_impls {
let tpl = UntypedTuple::try_from_expr(expr.clone()).await?;
let Some([$( [< $t:lower >], )*]) = tpl.0.iter().cloned().collect_array() else {
return Err(mk_errv(
i().i("Tuple arity mismatch").await,
is("Tuple arity mismatch").await,
format!("Expected a {}-ary tuple, found {}-ary", $len, tpl.0.len()),
[expr.data().await.pos.clone()]
));

View File

@@ -3,24 +3,26 @@ pub mod parse_folder;
use std::cell::RefCell;
use std::fs::File;
use std::io::{Read, Write};
use std::mem;
use std::process::{Command, ExitCode};
use std::rc::Rc;
use async_fn_stream::try_stream;
use camino::Utf8PathBuf;
use clap::{Parser, Subcommand};
use futures::future::LocalBoxFuture;
use futures::{FutureExt, Stream, TryStreamExt, io};
use itertools::Itertools;
use orchid_base::error::Reporter;
use orchid_base::format::{FmtCtxImpl, Format, fmt, take_first};
use orchid_base::error::{try_with_reporter, with_reporter};
use orchid_base::format::{FmtCtxImpl, Format, fmt, fmt_v, take_first};
use orchid_base::interner::local_interner::local_interner;
use orchid_base::interner::{is, with_interner};
use orchid_base::location::SrcRange;
use orchid_base::logging::{LogStrategy, Logger};
use orchid_base::logging::{LogStrategy, Logger, with_logger};
use orchid_base::name::{NameLike, VPath};
use orchid_base::parse::{Import, Snippet};
use orchid_base::sym;
use orchid_base::tree::{Token, ttv_fmt};
use orchid_host::ctx::Ctx;
use orchid_host::ctx::{Ctx, JoinHandle, Spawner};
use orchid_host::execute::{ExecCtx, ExecResult};
use orchid_host::expr::ExprKind;
use orchid_host::extension::Extension;
@@ -78,84 +80,87 @@ pub enum Commands {
fn get_all_extensions<'a>(
args: &'a Args,
logger: &'a Logger,
msg_logger: &'a Logger,
ctx: &'a Ctx,
) -> impl Stream<Item = io::Result<Extension>> + 'a {
try_stream(async |mut cx| {
for ext_path in args.extension.iter() {
let exe = if cfg!(windows) { ext_path.with_extension("exe") } else { ext_path.clone() };
let init =
ext_command(Command::new(exe.as_os_str()), logger.clone(), msg_logger.clone(), ctx.clone())
.await?;
cx.emit(Extension::new(init, logger.clone(), msg_logger.clone(), ctx.clone())?).await;
let init = ext_command(Command::new(exe.as_os_str()), ctx.clone()).await?;
cx.emit(Extension::new(init, ctx.clone()).await?).await;
}
Ok(cx)
})
}
struct JoinHandleImpl(tokio::task::JoinHandle<()>);
impl JoinHandle for JoinHandleImpl {
fn abort(&self) { self.0.abort() }
fn join(self: Box<Self>) -> LocalBoxFuture<'static, ()> {
Box::pin(async { self.0.await.unwrap() })
}
}
struct SpawnerImpl;
impl Spawner for SpawnerImpl {
fn spawn_obj(&self, fut: LocalBoxFuture<'static, ()>) -> Box<dyn JoinHandle> {
Box::new(JoinHandleImpl(spawn_local(fut)))
}
}
#[tokio::main]
async fn main() -> io::Result<ExitCode> {
eprintln!("orcx launched");
let exit_code = Rc::new(RefCell::new(ExitCode::SUCCESS));
let local_set = LocalSet::new();
let exit_code1 = exit_code.clone();
let args = Args::parse();
let logger = Logger::new(if args.logs { LogStrategy::StdErr } else { LogStrategy::Discard });
let cx_logger = logger.clone();
let msg_logger =
Logger::new(if args.msg_logs { LogStrategy::StdErr } else { LogStrategy::Discard });
local_set.spawn_local(async move {
let args = Args::parse();
let ctx = &Ctx::new(Rc::new(|fut| mem::drop(spawn_local(fut))));
let i = &ctx.i;
let logger = Logger::new(if args.logs { LogStrategy::StdErr } else { LogStrategy::Discard });
let msg_logger =
Logger::new(if args.msg_logs { LogStrategy::StdErr } else { LogStrategy::Discard });
let extensions = get_all_extensions(&args, &logger, &msg_logger, ctx)
.try_collect::<Vec<Extension>>()
.await
.unwrap();
let ctx = &Ctx::new(msg_logger.clone(), SpawnerImpl);
let extensions = get_all_extensions(&args, ctx).try_collect::<Vec<Extension>>().await.unwrap();
match args.command {
Commands::Lex { file } => {
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
let mut file = File::open(file.as_std_path()).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let lexemes = lex(i.i(&buf).await, sym!(usercode; i), &systems, ctx).await.unwrap();
println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i }).await, true))
let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap();
println!("{}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true))
},
Commands::Parse { file } => {
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
let mut file = File::open(file.as_std_path()).unwrap();
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
let lexemes = lex(i.i(&buf).await, sym!(usercode; i), &systems, ctx).await.unwrap();
let lexemes = lex(is(&buf).await, sym!(usercode), &systems, ctx).await.unwrap();
let Some(first) = lexemes.first() else {
println!("File empty!");
return;
};
let reporter = Reporter::new();
let pctx = HostParseCtxImpl {
rep: &reporter,
systems: &systems,
ctx: ctx.clone(),
src: sym!(usercode; i),
};
let pctx = HostParseCtxImpl { systems: &systems, ctx: ctx.clone(), src: sym!(usercode) };
let snip = Snippet::new(first, &lexemes);
let ptree = parse_items(&pctx, Substack::Bottom, snip).await.unwrap();
if let Some(errv) = reporter.errv() {
eprintln!("{errv}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
}
if ptree.is_empty() {
eprintln!("File empty only after parsing, but no errors were reported");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
}
for item in ptree {
println!("{}", take_first(&item.print(&FmtCtxImpl { i }).await, true))
}
match with_reporter(parse_items(&pctx, Substack::Bottom, snip)).await.unwrap() {
Err(errv) => {
eprintln!("{errv}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
},
Ok(ptree) if ptree.is_empty() => {
eprintln!("File empty only after parsing, but no errors were reported");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
},
Ok(ptree) =>
for item in ptree {
println!("{}", take_first(&item.print(&FmtCtxImpl::default()).await, true))
},
};
},
Commands::Repl => {
let mut counter = 0;
let mut imports = Vec::new();
let usercode_path = sym!(usercode; i);
let usercode_path = sym!(usercode);
let mut stdin = BufReader::new(stdin());
loop {
counter += 1;
@@ -164,26 +169,24 @@ async fn main() -> io::Result<ExitCode> {
std::io::stdout().flush().unwrap();
let mut prompt = String::new();
stdin.read_line(&mut prompt).await.unwrap();
let name = i.i(&format!("_{counter}")).await;
let path = usercode_path.suffix([name.clone()], i).await;
let name = is(&format!("_{counter}")).await;
let path = usercode_path.suffix([name.clone()]).await;
let mut lexemes =
lex(i.i(prompt.trim()).await, path.clone(), &systems, ctx).await.unwrap();
lex(is(prompt.trim()).await, path.clone(), &systems, ctx).await.unwrap();
let Some(discr) = lexemes.first() else { continue };
if args.logs {
println!("lexed: {}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i }).await, true));
println!(
"lexed: {}",
take_first(&ttv_fmt(&lexemes, &FmtCtxImpl::default()).await, true)
);
}
let prefix_sr = SrcRange::zw(path.clone(), 0);
let process_lexemes = async |lexemes: &[ParsTokTree]| {
let snippet = Snippet::new(&lexemes[0], lexemes);
let reporter = Reporter::new();
let parse_ctx = HostParseCtxImpl {
ctx: ctx.clone(),
rep: &reporter,
src: path.clone(),
systems: &systems[..],
};
let parse_result = parse_item(&parse_ctx, Substack::Bottom, vec![], snippet).await;
match reporter.merge(parse_result) {
let parse_ctx =
HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] };
match try_with_reporter(parse_item(&parse_ctx, Substack::Bottom, vec![], snippet)).await
{
Ok(items) => Some(items),
Err(e) => {
eprintln!("{e}");
@@ -194,7 +197,7 @@ async fn main() -> io::Result<ExitCode> {
let add_imports = |items: &mut Vec<Item>, imports: &[Import]| {
items.extend(imports.iter().map(|import| Item::new(import.sr.clone(), import.clone())));
};
if discr.is_kw(i.i("import").await) {
if discr.is_kw(is("import").await) {
let Some(import_lines) = process_lexemes(&lexemes).await else { continue };
imports.extend(import_lines.into_iter().map(|it| match it.kind {
ItemKind::Import(imp) => imp,
@@ -202,8 +205,8 @@ async fn main() -> io::Result<ExitCode> {
}));
continue;
}
if !discr.is_kw(i.i("let").await) {
let prefix = [i.i("export").await, i.i("let").await, name.clone(), i.i("=").await];
if !discr.is_kw(is("let").await) {
let prefix = [is("export").await, is("let").await, name.clone(), is("=").await];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
}
let Some(mut new_lines) = process_lexemes(&lexemes).await else { continue };
@@ -216,29 +219,36 @@ async fn main() -> io::Result<ExitCode> {
add_imports(&mut new_lines, &imports);
imports.push(Import::new(input_sr.clone(), VPath::new(path.segs()), const_name.clone()));
let new_module = ParsedModule::new(true, new_lines);
let reporter = Reporter::new();
root = root.add_parsed(&new_module, path.clone(), &reporter).await;
match with_reporter(root.add_parsed(&new_module, path.clone())).await {
Ok(new) => root = new,
Err(errv) => {
eprintln!("{errv}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
}
eprintln!("parsed");
let entrypoint =
ExprKind::Const(path.suffix([const_name.clone()], i).await).at(input_sr.pos());
let mut xctx = ExecCtx::new(ctx.clone(), logger.clone(), root.clone(), entrypoint).await;
ExprKind::Const(path.suffix([const_name.clone()]).await).at(input_sr.pos());
let mut xctx = ExecCtx::new(root.clone(), entrypoint).await;
eprintln!("executed");
xctx.set_gas(Some(1000));
xctx.execute().await;
match xctx.result() {
ExecResult::Value(val) =>
println!("{const_name} = {}", take_first(&val.print(&FmtCtxImpl { i }).await, false)),
ExecResult::Value(val) => println!(
"{const_name} = {}",
take_first(&val.print(&FmtCtxImpl::default()).await, false)
),
ExecResult::Err(e) => println!("error: {e}"),
ExecResult::Gas(_) => println!("Ran out of gas!"),
}
}
},
Commands::ModTree { proj, prefix } => {
let reporter = Reporter::new();
let (mut root, _systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = proj {
let path = proj_path.into_std_path_buf();
match parse_folder(&root, path, sym!(src; i), &reporter, ctx.clone()).await {
match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await {
Ok(r) => root = r,
Err(e) => {
eprintln!("{e}");
@@ -248,7 +258,7 @@ async fn main() -> io::Result<ExitCode> {
}
}
let prefix = match prefix {
Some(pref) => VPath::parse(&pref, i).await,
Some(pref) => VPath::parse(&pref).await,
None => VPath::new([]),
};
let root_data = root.0.read().await;
@@ -265,7 +275,7 @@ async fn main() -> io::Result<ExitCode> {
}
}
for (key, mem) in &module.members {
let new_path = path.clone().name_with_suffix(key.clone()).to_sym(&root.ctx.i).await;
let new_path = path.clone().name_with_suffix(key.clone()).to_sym().await;
match mem.kind(root.ctx.clone(), &root.consts).await {
MemberKind::Module(module) => {
println!("{indent}module {key} {{");
@@ -274,20 +284,20 @@ async fn main() -> io::Result<ExitCode> {
},
MemberKind::Const => {
let value = root.consts.get(&new_path).expect("Missing const!");
println!("{indent}const {key} = {}", fmt(value, &root.ctx.i).await)
println!("{indent}const {key} = {}", fmt(value).await)
},
}
}
}
},
Commands::Exec { proj, code } => {
let reporter = Reporter::new();
let path = sym!(usercode; i);
eprintln!("exec branch");
let path = sym!(usercode);
let prefix_sr = SrcRange::zw(path.clone(), 0);
let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap();
if let Some(proj_path) = proj {
let path = proj_path.into_std_path_buf();
match parse_folder(&root, path, sym!(src; i), &reporter, ctx.clone()).await {
match try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone())).await {
Ok(r) => root = r,
Err(e) => {
eprintln!("{e}");
@@ -296,22 +306,32 @@ async fn main() -> io::Result<ExitCode> {
},
}
}
let mut lexemes = lex(i.i(code.trim()).await, path.clone(), &systems, ctx).await.unwrap();
if args.logs {
println!("lexed: {}", take_first(&ttv_fmt(&lexemes, &FmtCtxImpl { i }).await, true));
}
let parse_ctx = HostParseCtxImpl {
ctx: ctx.clone(),
rep: &reporter,
src: path.clone(),
systems: &systems[..],
let mut lexemes = match lex(is(code.trim()).await, path.clone(), &systems, ctx).await {
Ok(lexemes) => {
if args.logs {
println!("lexed: {}", fmt_v::<ParsTokTree>(lexemes.iter()).await.join(" "));
}
lexemes
},
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
};
let prefix =
[i.i("export").await, i.i("let").await, i.i("entrypoint").await, i.i("=").await];
let parse_ctx =
HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] };
let prefix = [is("export").await, is("let").await, is("entrypoint").await, is("=").await];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
let snippet = Snippet::new(&lexemes[0], &lexemes);
let parse_res = parse_item(&parse_ctx, Substack::Bottom, vec![], snippet).await;
let entrypoint = match reporter.merge(parse_res) {
let entrypoint = match try_with_reporter(parse_item(
&parse_ctx,
Substack::Bottom,
vec![],
snippet,
))
.await
{
Ok(items) => ParsedModule::new(true, items),
Err(e) => {
eprintln!("{e}");
@@ -319,22 +339,28 @@ async fn main() -> io::Result<ExitCode> {
return;
},
};
let reporter = Reporter::new();
let root = root.add_parsed(&entrypoint, path.clone(), &reporter).await;
let expr = ExprKind::Const(sym!(usercode::entrypoint; i)).at(prefix_sr.pos());
let mut xctx = ExecCtx::new(ctx.clone(), logger.clone(), root, expr).await;
let root = match with_reporter(root.add_parsed(&entrypoint, path.clone())).await {
Err(e) => {
eprintln!("{e}");
*exit_code1.borrow_mut() = ExitCode::FAILURE;
return;
},
Ok(new_root) => new_root,
};
let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos());
let mut xctx = ExecCtx::new(root, expr).await;
xctx.set_gas(Some(10_000));
xctx.execute().await;
match xctx.result() {
ExecResult::Value(val) =>
println!("{}", take_first(&val.print(&FmtCtxImpl { i }).await, false)),
println!("{}", take_first(&val.print(&FmtCtxImpl::default()).await, false)),
ExecResult::Err(e) => println!("error: {e}"),
ExecResult::Gas(_) => println!("Ran out of gas!"),
}
},
}
});
local_set.await;
with_interner(local_interner(), with_logger(cx_logger, local_set)).await;
let x = *exit_code.borrow();
Ok(x)
}

View File

@@ -3,7 +3,8 @@ use std::path::{Path, PathBuf};
use futures::FutureExt;
use itertools::Itertools;
use orchid_base::error::{OrcRes, Reporter, async_io_err, os_str_to_string};
use orchid_base::error::{OrcRes, async_io_err, os_str_to_string, report};
use orchid_base::interner::is;
use orchid_base::location::SrcRange;
use orchid_base::name::Sym;
use orchid_base::parse::Snippet;
@@ -16,22 +17,16 @@ use substack::Substack;
use tokio::fs::{self, File};
use tokio::io::AsyncReadExt;
pub async fn parse_folder(
root: &Root,
path: PathBuf,
ns: Sym,
rep: &Reporter,
ctx: Ctx,
) -> OrcRes<Root> {
let parsed_module = (recur(&path, ns.clone(), rep, ctx).await?)
.expect("Project folder is a single non-orchid file");
return Ok(root.add_parsed(&parsed_module, ns, rep).await);
async fn recur(path: &Path, ns: Sym, rep: &Reporter, ctx: Ctx) -> OrcRes<Option<ParsedModule>> {
pub async fn parse_folder(root: &Root, path: PathBuf, ns: Sym, ctx: Ctx) -> OrcRes<Root> {
let parsed_module =
(recur(&path, ns.clone(), ctx).await?).expect("Project folder is a single non-orchid file");
return Ok(root.add_parsed(&parsed_module, ns).await);
async fn recur(path: &Path, ns: Sym, ctx: Ctx) -> OrcRes<Option<ParsedModule>> {
let sr = SrcRange::new(0..0, &ns);
if path.is_dir() {
let mut items = Vec::new();
let mut stream = match fs::read_dir(path).await {
Err(err) => return Err(async_io_err(err, &ctx.i, [sr]).await),
Err(err) => return Err(async_io_err(err, [sr]).await),
Ok(s) => s,
};
loop {
@@ -39,17 +34,17 @@ pub async fn parse_folder(
Ok(Some(ent)) => ent,
Ok(None) => break,
Err(err) => {
rep.report(async_io_err(err, &ctx.i, [sr.clone()]).await);
report(async_io_err(err, [sr.clone()]).await);
continue;
},
};
let os_name = entry.path().file_stem().expect("File name could not be read").to_owned();
let name = ctx.i.i(os_str_to_string(&os_name, &ctx.i, [sr.clone()]).await?).await;
let ns = ns.suffix([name.clone()], &ctx.i).await;
let name = is(os_str_to_string(&os_name, [sr.clone()]).await?).await;
let ns = ns.suffix([name.clone()]).await;
let sr = SrcRange::new(0..0, &ns);
match recur(&entry.path(), ns.clone(), rep, ctx.clone()).boxed_local().await {
match recur(&entry.path(), ns.clone(), ctx.clone()).boxed_local().await {
Err(e) => {
rep.report(e);
report(e);
continue;
},
Ok(None) => continue,
@@ -59,17 +54,17 @@ pub async fn parse_folder(
Ok(Some(ParsedModule::new(false, items)))
} else if path.extension() == Some(OsStr::new("orc")) {
let mut file = match File::open(path).await {
Err(e) => return Err(async_io_err(e, &ctx.i, [sr]).await),
Err(e) => return Err(async_io_err(e, [sr]).await),
Ok(file) => file,
};
let mut text = String::new();
if let Err(e) = file.read_to_string(&mut text).await {
return Err(async_io_err(e, &ctx.i, [sr]).await);
return Err(async_io_err(e, [sr]).await);
}
let systems =
ctx.systems.read().await.iter().filter_map(|(_, sys)| sys.upgrade()).collect_vec();
let lexemes = lex(ctx.i.i(&text).await, ns.clone(), &systems, &ctx).await?;
let hpctx = HostParseCtxImpl { ctx: ctx.clone(), rep, src: ns.clone(), systems: &systems };
let lexemes = lex(is(&text).await, ns.clone(), &systems, &ctx).await?;
let hpctx = HostParseCtxImpl { ctx: ctx.clone(), src: ns.clone(), systems: &systems };
let Some(fst) = lexemes.first() else { return Ok(Some(ParsedModule::new(false, []))) };
let items = parse_items(&hpctx, Substack::Bottom, Snippet::new(fst, &lexemes)).await?;
Ok(Some(ParsedModule::new(true, items)))