Skip to content

Instantly share code, notes, and snippets.

@frondeus
Last active October 29, 2019 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save frondeus/f7a14a4473506c26fac1280a6220a658 to your computer and use it in GitHub Desktop.
Save frondeus/f7a14a4473506c26fac1280a6220a658 to your computer and use it in GitHub Desktop.
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