Last active
October 29, 2019 14:01
-
-
Save frondeus/f7a14a4473506c26fac1280a6220a658 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use self::problem::{DeadEnd, Problem, Context}; | |
mod problem { | |
use std::fmt::Debug; | |
pub trait Problem: Debug {} | |
pub trait Context: Debug {} | |
#[derive(Debug)] | |
pub struct DeadEnd<P, C> { problem: P, context: Vec<C> } | |
impl<P, C> DeadEnd<P, C> { | |
pub fn new(problem: P) -> Self { | |
Self { problem, context: vec![] } | |
} | |
pub fn switch<C2: From<C>>(self) -> DeadEnd<P, C2> { | |
let context = self.context.into_iter() | |
.map(|c| C2::from(c)) | |
.collect(); | |
DeadEnd { problem: self.problem, context } | |
} | |
pub fn add_context(&mut self, c: C) { self.context.insert(0, c); } | |
} | |
} | |
#[derive(Debug)] | |
enum NoProblem {} | |
impl Problem for NoProblem {} | |
#[derive(Debug)] | |
enum CharProblem { Test } | |
impl Problem for CharProblem {} | |
#[derive(Debug)] | |
enum NoContext {} | |
impl Context for CharProblem {} | |
type Result<'a, T, P = NoProblem, C = NoContext> = std::result::Result<(T, &'a str), DeadEnd<P, C>>; | |
fn take<'a>(len: usize) -> impl Fn(&'a str) -> Result<&'a str, CharProblem> { | |
move |input| { | |
if input.len() < len { | |
return Err(DeadEnd::new(CharProblem::Test)); | |
} | |
Ok((&input[0..len], &input[len..])) | |
} | |
} | |
fn and<'a, P1, P2, T1, T2, C1, C2, P>(p1: P1, p2: P2) | |
-> impl Fn(&'a str) -> Result<(T1, T2), P, C1> | |
where P1: Fn(&'a str) -> Result<T1, P, C1>, | |
P2: Fn(&'a str) -> Result<T2, P, C2>, | |
C1: From<C2> | |
{ | |
move |i| { | |
let (p1, i) = p1(i).map_err(DeadEnd::switch)?; | |
let (p2, i) = p2(i).map_err(DeadEnd::switch)?; | |
let t = (p1, p2); | |
Ok((t, i)) | |
} | |
} | |
fn map<'a, P1, F, T1, T2, P, C>(p: P1, f: F) | |
-> impl Fn(&'a str) -> Result<T2, P, C> | |
where P1: Fn(&'a str) -> Result<T1, P, C>, | |
F: Fn(T1) -> T2 | |
{ | |
move |i| { | |
let (p1, i) = p(i)?; | |
let p2 = f(p1); | |
let t = p2; | |
Ok((t, i)) | |
} | |
} | |
fn context<'a, P1, T, P, C1, C2>(c: C2, p: P1) | |
-> impl Fn(&'a str) -> Result<T, P, C2> | |
where P1: Fn(&'a str) -> Result<T, P, C1>, | |
C2: From<C1> + Copy | |
{ | |
move |i| { | |
p(i).map_err(|e| { | |
let mut e = e.switch(); | |
e.add_context(c); | |
e | |
}) | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
use test_case::test_case; // Im using my local version of test-case library | |
// with extra experimental features like `matches` and `panics` | |
use std::fmt::Debug; | |
#[derive(Debug)] | |
struct Foo<'a>(&'a str, &'a str); | |
#[derive(Debug, Clone, Copy)] | |
enum BarContext { Bar } | |
impl Context for BarContext {} | |
impl From<NoContext> for BarContext { fn from(_: NoContext) -> Self { unreachable!() } } | |
#[derive(Debug, Clone, Copy)] | |
enum BazContext { Bar(BarContext), Baz } | |
impl Context for BazContext {} | |
impl From<NoContext> for BazContext { fn from(_: NoContext) -> Self { unreachable!() } } | |
impl From<BarContext> for BazContext { | |
fn from(f: BarContext) -> Self { | |
BazContext::Bar(f) | |
} | |
} | |
fn foo<'a>() -> impl Fn(&'a str) -> Result<Foo<'a>, CharProblem> { | |
map(and(take(1), take(2)), |(t1, t2)| Foo(t1, t2)) | |
} | |
fn bar<'a>() -> impl Fn(&'a str) -> Result<Foo<'a>, CharProblem, BarContext> { | |
context(BarContext::Bar, foo()) | |
} | |
fn baz<'a>() -> impl Fn(&'a str) -> Result<(Foo<'a>, &'a str), CharProblem, BazContext> { | |
context(BazContext::Baz, | |
and(bar(), take(1)) | |
) | |
} | |
#[test_case(take(1), "abc" => matches ("a", _) )] | |
#[test_case(foo(), "abc" => matches ( Foo("a", "bc"), _) )] | |
#[test_case(bar(), "abc" => matches ( Foo("a", "bc"), _) )] | |
#[test_case(bar(), "ab" => panics "[Bar]" )] | |
#[test_case(baz(), "" => panics "[Baz, Bar(Bar)]" )] | |
fn test<'a, T: Debug, Problem: Debug, Context: Debug>( | |
p: impl Fn(&'a str) -> Result<'a, T, Problem, Context>, | |
input: &'a str) -> (T, &'a str) { | |
p(input).unwrap() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment