diff --git a/Cargo.toml b/Cargo.toml index a7aac59..56b6fe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,7 @@ edition = "2021" [dependencies] futures = "0.3.26" +hex = "0.4.3" + +[dev-dependencies] +sha2 = "0.10.6" diff --git a/src/core.rs b/src/core.rs index 0b048a2..cb70876 100644 --- a/src/core.rs +++ b/src/core.rs @@ -12,7 +12,7 @@ pub trait Context { type T: Monad; /// Type to represent resolution errors mainly arising in [`Resolver::resolve`]. - type LookupError: Error; + type LookupError<'a>: 'a + Error; /// Get [type@Hash] of a slice, mostly for use in [`Point`]. fn hash(s: &[u8]) -> Hash; @@ -28,7 +28,7 @@ pub enum ResolutionError { pub type ResolutionResult<'a, Ctx, A> = Result< Rc, ResolutionError< - ::LookupError, + ::LookupError<'a>, <>::Fctr as Factory<'a, Ctx>>::ParseError, >, >; @@ -99,10 +99,10 @@ pub trait Mentionable<'a, Ctx: 'a + Context>: 'a + Serializable { /// Short-hand for the type of values returned by [`Resolver::resolve`]. pub type HashResolution<'a, Ctx> = - Wrapped<'a, Ctx, Result<(Vec, Rc>), ::LookupError>>; + Wrapped<'a, Ctx, Result<(Vec, Rc>), ::LookupError<'a>>>; /// Value accepted by [`Resolver::resolve`]. Includes index to make it order-sensitive. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct Address { pub point: Hash, /// Index of the point in the [`Mentionable::points()`]. @@ -165,12 +165,12 @@ struct LocalOrigin(Rc); impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Origin<'a, Ctx> for LocalOrigin { type Mtbl = A; - fn factory(&self) -> >::Fctr { + fn factory(&self) -> A::Fctr { self.0.factory() } fn resolve(self: Rc) -> Resolution<'a, Ctx, Self::Mtbl> { - ::pure(Ok(self.0.clone())) + Ctx::T::pure(Ok(self.0.clone())) } } @@ -289,7 +289,7 @@ pub struct TypelessError<'a>(Box); impl<'a> Display for TypelessError<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) + f.write_fmt(format_args!("typeless error: {}", self.0)) } } diff --git a/src/lib.rs b/src/lib.rs index 0e1cd37..8e5afd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,4 +2,6 @@ pub mod core; pub mod func; pub mod std; #[cfg(test)] +mod testing; +#[cfg(test)] mod xrcs; diff --git a/src/std.rs b/src/std.rs index c3866b3..2e68497 100644 --- a/src/std.rs +++ b/src/std.rs @@ -1,5 +1,6 @@ pub mod atomic; pub mod cast; +pub mod collections; pub mod inlining; pub mod nullable; @@ -135,7 +136,7 @@ impl Display for PointParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { PointParseError::WrongLength(length) => f.write_fmt(format_args!( - "expected {} bytes, received {}", + "expected {} bytes, received {}.", HASH_SIZE, length )), } @@ -335,3 +336,21 @@ impl ExtDeserializer for D { } } } + +impl Display for Address { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}@{}", hex::encode(self.point), self.index)) + } +} + +pub trait ExtSerializable: Serializable { + fn to_vec(&self) -> Vec; +} + +impl ExtSerializable for S { + fn to_vec(&self) -> Vec { + let mut vec = Vec::new(); + self.serialize(&mut vec); + vec + } +} diff --git a/src/std/atomic.rs b/src/std/atomic.rs index 9415821..cee41cf 100644 --- a/src/std/atomic.rs +++ b/src/std/atomic.rs @@ -1,9 +1,12 @@ //! This module allows to describe a primitive subset of [Mentionable] types, [Atomic]s, //! simple static types, which are completely [Context]-independent. +pub mod plain; + use std::{error::Error, marker::PhantomData, rc::Rc}; use crate::core::*; +use crate::std::*; /// This trait combines functionality of [`Mentionable`] and [`Factory`], /// while limiting [`Mentionable::points`] (and corresponding [`Mentionable::topology`]) to an empty sequence. @@ -64,3 +67,29 @@ impl<'a, Ctx: 'a + Context, A: Atomic> Factory<'a, Ctx> for AtomicFactory { A::unexpected_tail(tail) } } + +fn _parse_slice(slice: &[u8]) -> Result { + let mut deserializer = SliceDeserializer::from(slice); + let mentionable = A::deserialize(&mut deserializer)?; + let tail = deserializer.read_all(); + if tail.is_empty() { + Ok(mentionable) + } else { + Err(A::unexpected_tail(tail)) + } +} + +pub trait ExtAtomic: Atomic { + fn f() -> AtomicFactory; + fn parse_slice(slice: &[u8]) -> Result; +} + +impl ExtAtomic for A { + fn f() -> AtomicFactory { + AtomicFactory::new() + } + + fn parse_slice(slice: &[u8]) -> Result { + _parse_slice(slice) + } +} diff --git a/src/std/atomic/plain.rs b/src/std/atomic/plain.rs new file mode 100644 index 0000000..d462202 --- /dev/null +++ b/src/std/atomic/plain.rs @@ -0,0 +1,55 @@ +use std::fmt::Display; + +use crate::std::atomic::*; + +#[derive(Clone)] +struct Plain { + pub data: Vec, +} + +#[derive(Debug)] +enum PlainParseError {} + +impl Display for PlainParseError { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self {} + } +} + +impl Error for PlainParseError {} + +impl Serializable for Plain { + fn serialize(&self, serializer: &mut dyn Serializer) { + serializer.write(&self.data) + } +} + +impl Atomic for Plain { + type ParseError = PlainParseError; + + fn deserialize(deserializer: &mut dyn Deserializer) -> Result { + Ok(Plain { + data: deserializer.read_all().into(), + }) + } + + fn unexpected_tail(tail: &[u8]) -> Self::ParseError { + panic!( + "Plain must use read_all, therefore there must not be any extra tail (received {} bytes).", + tail.len() + ) + } +} + +#[cfg(test)] +mod tests { + use crate::std::*; + use super::*; + + #[test] + fn test_plain() -> Result<(), PlainParseError> { + let plain = Plain::parse_slice(b"slice")?; + assert_eq!(plain.to_vec().as_slice(), b"slice"); + Ok(()) + } +} diff --git a/src/std/cast.rs b/src/std/cast.rs index 1d744db..4521116 100644 --- a/src/std/cast.rs +++ b/src/std/cast.rs @@ -17,7 +17,7 @@ pub enum CastError<'a> { impl<'a, Ctx: 'a + Context> Resolver<'a, Ctx> for CastResolver<'a, Ctx> where - Ctx::LookupError: From>, + Ctx::LookupError<'a>: From>, { fn resolve(self: Rc, address: Address) -> HashResolution<'a, Ctx> { let point = match self.points.get(address.index) { @@ -37,12 +37,10 @@ where Ctx::T::fmap( |resolved| match resolved { Ok(mentionable) => { - let mut vec = Vec::new(); - mentionable.serialize(&mut vec); let resolver: Rc> = Rc::new(CastResolver { points: mentionable.points(), }); - Ok((vec, resolver)) + Ok((mentionable.to_vec(), resolver)) } Err(error) => Err(match error { ResolutionError::Lookup(lookup_error) => lookup_error, @@ -58,13 +56,11 @@ pub type CastResult<'a, Ctx, A> = Result>::Fctr as Factory<'a, Ctx>>::ParseError>; impl<'a, Ctx: Context> TypelessMentionable<'a, Ctx> where - Ctx::LookupError: From>, + Ctx::LookupError<'a>: From>, { pub fn cast>(&self, factory: A::Fctr) -> CastResult<'a, Ctx, A> { - let mut vec = Vec::new(); - self.serialize(&mut vec); factory.parse_slice( - &vec, + &self.to_vec(), Rc::new(CastResolver { points: self.points(), }), diff --git a/src/std/collections.rs b/src/std/collections.rs new file mode 100644 index 0000000..c93897d --- /dev/null +++ b/src/std/collections.rs @@ -0,0 +1 @@ +pub mod stack; diff --git a/src/std/collections/stack.rs b/src/std/collections/stack.rs new file mode 100644 index 0000000..728c2fa --- /dev/null +++ b/src/std/collections/stack.rs @@ -0,0 +1,129 @@ +use crate::core::*; +use crate::std::nullable::*; +use crate::std::*; + +pub struct StackNode<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> { + pub rest: Stack<'a, Ctx, A>, + pub element: A, +} + +type Stack<'a, Ctx, A> = Nullable<'a, Ctx, StackNode<'a, Ctx, A>>; + +pub struct StackNodeFactory<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> { + element_factory: A::Fctr, +} + +impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> StackNodeFactory<'a, Ctx, A> { + fn new(factory: A::Fctr) -> Self { + StackNodeFactory { + element_factory: factory, + } + } +} + +impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Serializable for StackNode<'a, Ctx, A> { + fn serialize(&self, serializer: &mut dyn Serializer) { + self.rest.serialize(serializer); + self.element.serialize(serializer); + } +} + +impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Mentionable<'a, Ctx> + for StackNode<'a, Ctx, A> +{ + type Fctr = StackNodeFactory<'a, Ctx, A>; + + fn factory(&self) -> Self::Fctr { + StackNodeFactory::new(self.element.factory()) + } + + fn points(&self) -> Vec>> { + let mut points = self.rest.points(); + points.extend(self.element.points()); + points + } +} + +impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Clone for StackNodeFactory<'a, Ctx, A> { + fn clone(&self) -> Self { + StackNodeFactory::new(self.element_factory.clone()) + } +} + +#[derive(Debug)] +pub enum StackParseError { + Point(PointParseError), + Element(ElementParseError), +} + +impl Display for StackParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StackParseError::Point(ppe) => { + f.write_fmt(format_args!("can't parse stack's next pointer: {}", ppe)) + } + StackParseError::Element(epe) => { + f.write_fmt(format_args!("can't parse stack's element: {}", epe)) + } + } + } +} + +impl Error for StackParseError {} + +impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Factory<'a, Ctx> + for StackNodeFactory<'a, Ctx, A> +{ + type Mtbl = StackNode<'a, Ctx, A>; + + type ParseError = StackParseError<>::ParseError>; + + fn deserialize( + &self, + deserializer: &mut dyn Deserializer, + resolver: Rc>, + ) -> ParseResult<'a, Ctx, Self> { + let rest = + match NullableFactory::new(self.clone()).deserialize(deserializer, resolver.clone()) { + Ok(rest) => rest, + Err(ppe) => { + return Err(StackParseError::Point(ppe)); + } + }; + let element = match self.element_factory.deserialize(deserializer, resolver) { + Ok(element) => element, + Err(epe) => { + return Err(StackParseError::Element(epe)); + } + }; + Ok(StackNode { rest, element }) + } + + fn unexpected_tail(&self, tail: &[u8]) -> Self::ParseError { + StackParseError::Element(self.element_factory.unexpected_tail(tail)) + } +} + +pub trait ExtStack<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>>: Mentionable<'a, Ctx> { + fn empty(factory: A::Fctr) -> Self; + fn f(factory: A::Fctr) -> Self::Fctr; + fn add(self, element: A) -> Self; +} + +impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> ExtStack<'a, Ctx, A> for Stack<'a, Ctx, A> { + fn empty(factory: A::Fctr) -> Self { + Nullable::Null(StackNodeFactory::new(factory.clone())) + } + fn f(factory: A::Fctr) -> Self::Fctr { + NullableFactory::new(StackNodeFactory::new(factory.clone())) + } + fn add(self, element: A) -> Self { + Nullable::NotNull( + StackNode { + rest: self, + element, + } + .into(), + ) + } +} diff --git a/src/std/nullable.rs b/src/std/nullable.rs index 757e849..64d81ae 100644 --- a/src/std/nullable.rs +++ b/src/std/nullable.rs @@ -79,3 +79,23 @@ impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Factory<'a, Ctx> PointParseError::WrongLength(HASH_SIZE + tail.len()) } } + +impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Nullable<'a, Ctx, Nullable<'a, Ctx, A>> { + pub fn join(&self) -> Resolution<'a, Ctx, Nullable<'a, Ctx, A>> { + match self { + Nullable::Null(nullable_factory) => { + let NullableFactory { factory } = nullable_factory; + Ctx::T::pure(Ok(Rc::new(Nullable::Null(factory.clone())))) + }, + Nullable::NotNull(point) => { + point.resolve() + }, + } + } +} + +impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> NullableFactory<'a, Ctx, A> { + pub fn new(factory: A::Fctr) -> Self { + Self { factory } + } +} diff --git a/src/testing.rs b/src/testing.rs new file mode 100644 index 0000000..48a19ca --- /dev/null +++ b/src/testing.rs @@ -0,0 +1,56 @@ +use std::error::Error; +use std::fmt::Display; + +use crate::core::*; +use crate::func::*; +use sha2::{Digest, Sha256}; + +pub struct TestContext; + +#[derive(Debug)] +pub enum TestLookupError<'a> { + Typeless(TypelessError<'a>), + EmptyResolverAccess(Address), +} + +impl<'a> From> for TestLookupError<'a> { + fn from(value: TypelessError<'a>) -> Self { + Self::Typeless(value) + } +} + +impl<'a> Display for TestLookupError<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TestLookupError::Typeless(typeless_error) => { + f.write_fmt(format_args!("typeless lookup failure: {}", typeless_error)) + } + TestLookupError::EmptyResolverAccess(address) => f.write_fmt(format_args!( + "accessed an empty resolved at address {}.", + address + )), + } + } +} + +impl<'a> Error for TestLookupError<'a> {} + +impl Context for TestContext { + type T = classes::solo::SoloClass; + + type LookupError<'a> = TestLookupError<'a>; + + fn hash(s: &[u8]) -> Hash { + let mut hasher = Sha256::new(); + hasher.update(s); + hasher.finalize().into() + } +} + +pub struct EmptyResolver; + +impl<'a> Resolver<'a, TestContext> for EmptyResolver { + fn resolve(self: std::rc::Rc, address: Address) -> HashResolution<'a, TestContext> { + Err(TestLookupError::EmptyResolverAccess(address)) + } +}