stack + testing

This commit is contained in:
AF 2023-04-22 13:14:38 +00:00
parent 4ababb524c
commit 8a3f9ac25f
11 changed files with 327 additions and 16 deletions

View File

@ -7,3 +7,7 @@ edition = "2021"
[dependencies]
futures = "0.3.26"
hex = "0.4.3"
[dev-dependencies]
sha2 = "0.10.6"

View File

@ -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<L, P> {
pub type ResolutionResult<'a, Ctx, A> = Result<
Rc<A>,
ResolutionError<
<Ctx as Context>::LookupError,
<Ctx as Context>::LookupError<'a>,
<<A as Mentionable<'a, Ctx>>::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<u8>, Rc<dyn Resolver<'a, Ctx>>), <Ctx as Context>::LookupError>>;
Wrapped<'a, Ctx, Result<(Vec<u8>, Rc<dyn Resolver<'a, Ctx>>), <Ctx as Context>::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<A>(Rc<A>);
impl<'a, Ctx: 'a + Context, A: Mentionable<'a, Ctx>> Origin<'a, Ctx> for LocalOrigin<A> {
type Mtbl = A;
fn factory(&self) -> <Self::Mtbl as Mentionable<'a, Ctx>>::Fctr {
fn factory(&self) -> A::Fctr {
self.0.factory()
}
fn resolve(self: Rc<Self>) -> Resolution<'a, Ctx, Self::Mtbl> {
<Ctx::T as Applicative>::pure(Ok(self.0.clone()))
Ctx::T::pure(Ok(self.0.clone()))
}
}
@ -289,7 +289,7 @@ pub struct TypelessError<'a>(Box<dyn 'a + Error>);
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))
}
}

View File

@ -2,4 +2,6 @@ pub mod core;
pub mod func;
pub mod std;
#[cfg(test)]
mod testing;
#[cfg(test)]
mod xrcs;

View File

@ -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<D: ?Sized + Deserializer> 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<u8>;
}
impl<S: Serializable> ExtSerializable for S {
fn to_vec(&self) -> Vec<u8> {
let mut vec = Vec::new();
self.serialize(&mut vec);
vec
}
}

View File

@ -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> {
A::unexpected_tail(tail)
}
}
fn _parse_slice<A: Atomic>(slice: &[u8]) -> Result<A, A::ParseError> {
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<Self>;
fn parse_slice(slice: &[u8]) -> Result<Self, Self::ParseError>;
}
impl<A: Atomic> ExtAtomic for A {
fn f() -> AtomicFactory<Self> {
AtomicFactory::new()
}
fn parse_slice(slice: &[u8]) -> Result<Self, Self::ParseError> {
_parse_slice(slice)
}
}

55
src/std/atomic/plain.rs Normal file
View File

@ -0,0 +1,55 @@
use std::fmt::Display;
use crate::std::atomic::*;
#[derive(Clone)]
struct Plain {
pub data: Vec<u8>,
}
#[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<Self, Self::ParseError> {
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(())
}
}

View File

@ -17,7 +17,7 @@ pub enum CastError<'a> {
impl<'a, Ctx: 'a + Context> Resolver<'a, Ctx> for CastResolver<'a, Ctx>
where
Ctx::LookupError: From<CastError<'a>>,
Ctx::LookupError<'a>: From<CastError<'a>>,
{
fn resolve(self: Rc<Self>, 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<dyn Resolver<'a, Ctx>> = 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<A, <<A as Mentionable<'a, Ctx>>::Fctr as Factory<'a, Ctx>>::ParseError>;
impl<'a, Ctx: Context> TypelessMentionable<'a, Ctx>
where
Ctx::LookupError: From<CastError<'a>>,
Ctx::LookupError<'a>: From<CastError<'a>>,
{
pub fn cast<A: Mentionable<'a, Ctx>>(&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(),
}),

1
src/std/collections.rs Normal file
View File

@ -0,0 +1 @@
pub mod stack;

View File

@ -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<Point<'a, Ctx, TypelessMentionable<'a, Ctx>>> {
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<ElementParseError: Error> {
Point(PointParseError),
Element(ElementParseError),
}
impl<ElementParseError: Error> Display for StackParseError<ElementParseError> {
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<ElementParseError: Error> Error for StackParseError<ElementParseError> {}
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<<A::Fctr as Factory<'a, Ctx>>::ParseError>;
fn deserialize(
&self,
deserializer: &mut dyn Deserializer,
resolver: Rc<dyn Resolver<'a, Ctx>>,
) -> 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(),
)
}
}

View File

@ -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 }
}
}

56
src/testing.rs Normal file
View File

@ -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<TypelessError<'a>> 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<Self>, address: Address) -> HashResolution<'a, TestContext> {
Err(TestLookupError::EmptyResolverAccess(address))
}
}