~jojo/microcrisp

18000161f4418b3d4702e870a39f91406956a61a — JoJo 8 months ago 2e0cf65
improve match_expr! & add try_match_expr!
1 files changed, 53 insertions(+), 29 deletions(-)

M src/matching.rs
M src/matching.rs => src/matching.rs +53 -29
@@ 1,4 1,4 @@
#[allow(unused_macros)]
#[macro_export]
macro_rules! match_case {
    // ==== GO! ====
    ($e:expr, @go {_}, $body:expr, $else:expr) => {


@@ 32,13 32,12 @@ macro_rules! match_case {
    };
    ($e:expr, @go {($($lhs:tt)*)}, $body:expr, $else:expr) => {{
        let r = match $e {
            $crate::Lisp::List(xs) => match_case!(xs, @munch {} {$($lhs)*}, $body, None),
            $crate::Lisp::List(xs) => $crate::match_case!(xs, @munch {} {$($lhs)*}, Some($body), None),
            _ => None,
        };
        if r.is_some() {
            r
        } else {
            $else
        match r {
            Some(r) => r,
            _ => $else
        }
    }};



@@ 47,22 46,21 @@ macro_rules! match_case {
        if $es.is_empty() { $body } else { $else }
    };
    ($es:expr, @munch {$($lhs:tt)+} {}, $body:expr, $else:expr) => {
        match_case!($es, @munch {$($lhs)+} {,}, $body, $else)
        $crate::match_case!($es, @munch {$($lhs)+} {,}, $body, $else)
    };
    ($es:expr, @munch {$($lhs:tt)+} {, $($lhss:tt)*}, $body:expr, $else:expr) => {{
        let r = if let Some((first, rest)) = $es.split_first() {
            match_case!(first, @go {$($lhs)+}, match_case!(rest, @munch {} {$($lhss)*}, $body, None), None)
            $crate::match_case!(first, @go {$($lhs)+}, $crate::match_case!(rest, @munch {} {$($lhss)*}, Some($body), None), None)
        } else {
            None
        };
        if r.is_some() {
            r
        } else {
            $else
        match r {
            Some(r) => r,
            _ => $else,
        }
    }};
    ($es:expr, @munch {$($lhs:tt)*} {$t:tt $($tts:tt)*}, $body:expr, $else:expr) => {
        match_case!($es, @munch {$($lhs)* $t} {$($tts)*}, $body, $else)
        $crate::match_case!($es, @munch {$($lhs)* $t} {$($tts)*}, $body, $else)
    };
}



@@ 70,22 68,45 @@ macro_rules! match_case {
macro_rules! match_expr {
    // ==== MUNCH! ====
    ($e:expr, @munch {$($lhs:tt)*} => $body:expr, $($clauses:tt)*) => {
        match_case!($e, @go {$($lhs)*}, Some($body), match_expr!($e, @munch {} $($clauses)*))
        $crate::match_case!($e, @go {$($lhs)*}, $body, $crate::match_expr!($e, @munch {} $($clauses)*))
    };
    ($e:expr, @munch {$($lhs:tt)*} => $body:expr) => {
        match_expr!($e, @munch {$($lhs)*} => $body ,)
        $crate::match_expr!($e, @munch {$($lhs)*} => $body ,)
    };
    ($e:expr, @munch {}) => {
        compile_error!("Missing catch-all case")
    };
    ($e:expr, @munch {$($lhs:tt)*} $t:tt $($tts:tt)*) => {
        $crate::match_expr!($e, @munch {$($lhs)* $t} $($tts)*)
    };

    // ==== ENTRY ====
    ($e:expr, { $($clauses:tt)* }) => {{
        let e = &$e;
        $crate::match_expr!(e, @munch {} $($clauses)*)
    }};
}

#[macro_export]
macro_rules! try_match_expr {
    // ==== MUNCH! ====
    ($e:expr, @munch {$($lhs:tt)*} => $body:expr, $($clauses:tt)*) => {
        $crate::match_case!($e, @go {$($lhs)*}, Some($body), $crate::try_match_expr!($e, @munch {} $($clauses)*))
    };
    ($e:expr, @munch {$($lhs:tt)*} => $body:expr) => {
        $crate::try_match_expr!($e, @munch {$($lhs)*} => $body ,)
    };
    ($e:expr, @munch {}) => {
        None
    };
    ($e:expr, @munch {$($lhs:tt)*} $t:tt $($tts:tt)*) => {
        match_expr!($e, @munch {$($lhs)* $t} $($tts)*)
        $crate::try_match_expr!($e, @munch {$($lhs)* $t} $($tts)*)
    };

    // ==== ENTRY ====
    ($e:expr, { $($clauses:tt)* }) => {{
        let e = &$e;
        match_expr!(e, @munch {} $($clauses)*)
        $crate::try_match_expr!(e, @munch {} $($clauses)*)
    }};
}



@@ 95,24 116,26 @@ mod test {

    #[test]
    fn match_expr() {
        assert_eq!(match_expr!((), { _ => "hello" }), Some("hello"));
        assert_eq!(match_expr!((), { _ => "hello" }), "hello");

        assert_eq!(match_expr!(Lisp::Int(1),   { 0 => "zero", _ => "nonzero" }), "nonzero");

        assert_eq!(match_expr!(Lisp::List(vec![]), { () => "yo", _ => "hello" }), Some("yo"));
        assert_eq!(match_expr!(Lisp::List(vec![]), { () => "yo", _ => "hello" }), "yo");

        assert_eq!(
            match_expr!(parse(r#" ("hello" "there") "#.as_bytes()).unwrap(), { (_, s) => s.clone() }),
            match_expr!(parse(r#" ("hello" "there") "#.as_bytes()).unwrap(), { (_, s) => Some(s.clone()), _ => None }),
            Some(Lisp::String("there".to_owned()))
        );

        assert_eq!(match_expr!(Lisp::Int(0),   { 0 => 666, 123 => 456 }), Some(666));
        assert_eq!(match_expr!(Lisp::Int(123), { 0 => 666, 123 => 456 }), Some(456));
        assert_eq!(match_expr!(Lisp::Int(123), { i(x) => *x }), Some(123));
        assert_eq!(match_expr!(Lisp::Int(0),   { 0 => 666, 123 => 456, _ => panic!("undefined") }), 666);
        assert_eq!(match_expr!(Lisp::Int(123), { 0 => Some(666), 123 => Some(456), _ => None }), Some(456));
        assert_eq!(try_match_expr!(Lisp::Int(123), { i(x) => *x }), Some(123));

        assert_eq!(match_expr!(parse(b"foo").unwrap(), { sy(s) => *s }), Symbol::pack_str("foo"));
        assert_eq!(match_expr!(parse(b"foo").unwrap(), { sy(_) => () }), Some(()));
        assert_eq!(match_expr!(parse(b"foo-bar").unwrap(), { 'foo_bar => () }), Some(()));
        assert_eq!(try_match_expr!(parse(b"foo").unwrap(), { sy(s) => *s }), Symbol::pack_str("foo"));
        assert_eq!(try_match_expr!(parse(b"foo").unwrap(), { sy(_) => () }), Some(()));
        assert!(match_expr!(parse(b"foo-bar").unwrap(), { 'foo_bar => true, _ => false }));

        assert_eq!(match_expr!(parse(b"(foo 123)").unwrap(), { ('foo, i(x)) => *x }), Some(123));
        assert_eq!(try_match_expr!(parse(b"(foo 123)").unwrap(), { ('foo, i(x)) => *x }), Some(123));

        #[derive(Debug, PartialEq)]
        enum Update {


@@ 122,8 145,9 @@ mod test {
        use Update::*;
        let v = match_expr!(parse(b"(version 1 2 3)").unwrap(), {
            ('version, i(major), i(minor), i(patch)) => Version(*major, *minor, *patch),
            'force => Force
            'force => Force,
            e => panic!("unexpected expression {e}")
        });
        assert_eq!(v, Some(Version(1, 2, 3)));
        assert_eq!(v, Version(1, 2, 3));
    }
}