1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
pub mod fn_stream;
pub mod io_stream;
pub type StreamGetFn<'a,T> = dyn FnMut() -> Option<T> + 'a; 

pub enum StreamResult<T> {
    Ok(T),
    Err(StreamError)
}

impl<T: fmt::Debug> fmt::Debug for StreamResult<T> {
    fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Ok(v) => write!(f,"Ok({:?})",v),
            Self::Err(e) => write!(f,"Err({:?})",e),
        }
    }
}

#[derive(Clone,Debug,PartialEq,Eq,Hash)]
pub enum StreamError {
    EmptyStream,
    NotHandledPattern,
    Str(String),
}

use std::fmt;
impl fmt::Display for StreamError {
    fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            StreamError::EmptyStream => write!(f,"empty stream"),
            StreamError::NotHandledPattern => write!(f,"stream pattern not handled"),
            StreamError::Str(s) => write!(f,"stream error: '{}'",s),
        }
    }
}


/// Suppose you need to process each line of a text file. One way to do this is to read the file in as a single large string and use something
/// like `split` to turn it into a list. This works when the file is small, but because the entire file is loaded into memory, it does not
/// scale well when the file is large.
/// 
/// More commonly, the read_line function can be used to read one line at a time from a channel. This typically looks like:
/// ```rust 
///     // LineReadStream is just example, you can implement your own stream for reading files.
///     use streams_rs::*;
///     use io_stream::*;
///     let mut c = std::io::Cursor::new(b"a\n b\n c");
///     let mut stream = LineReadStream::new(&mut c);
///     let _ = smatch!(match (stream) {
///         [a=> b=> c =>] => {
///             println!("{:?} {:?} {:?}",a,b,c);
///         } 
///     }); 
/// ```
/// 
pub trait Stream<'a> {
    type Item;
    /// Returns token at `x`. It's possible to cache values. FnStream for example caches all tokens returned from getter.
    fn token(&mut self,x: usize) -> StreamResult<Self::Item>;
    /// Basucally same as truncating vector.
    fn junk(&mut self,x: usize);
    /// Return stream position, does not used by macro or streams-rs, but might be usefull for someone.
    fn pos(&self) -> usize;
}

/// Macro for matching on streams. 
/// 
/// This macro will not catch any error for you so your getter should return `StreamResult<Result>` if you might have any errors. 
/// If value not matched then `StreamResult::Err(StreamError::EmptyStream)` is returned.
/// 
#[macro_export]
macro_rules! smatch {
    (@p $body: expr;$stream: expr,$cnt: expr;  -> $($pat: tt)*) => {{

            smatch!(@p $body; ; $stream,$cnt    ;$($pat)*)

    }
    };
    (@p $body: expr; $($assign: ident),*; $stream: expr,$cnt: expr; _ => $($rest:tt)*) => {
        {let res = $stream.token($cnt);
            match res {
                StreamResult::Ok(_) => {smatch!(@p $body; $name; $stream,$cnt + 1;$($rest)*)},
                _ => (None,false)
            }
        /*$(
            let $assign = $assign;
        )**/
        

        }
    };
    (@p $body: expr; $($assign: ident),*; $stream: expr,$cnt: expr; $name : ident => $($rest:tt)*) => {
        {let p = $stream.token($cnt);
        if let StreamResult::Ok($name) = p {
            smatch!(@p $body; $name; $stream,$cnt + 1;$($rest)*)
        } else {
            (None,false)
        }
        }
    };
    (@p $body:expr;$($assign: ident),*;$stream: expr,$cnt: expr; $p: pat => $($rest:tt)*) => {{
        let p = $stream.token($cnt);
        if let StreamResult::Ok($p) = p {
            
            $(
                let $assign = $assign;
            )*
            smatch!(@p $body; ; $stream,$cnt + 1;$($rest)*)
        } else {
            
            (None,false)
        }
    }
    };
    /*(@p $body: expr;$($assign: ident),*;$stream: expr,$cnt: expr;  $name : ident = $p: pat => $($rest:tt)*) => {
        {let x = $stream.token($cnt);
        if let $p = x {
            let $name = x;
            $(
                let $assign = $assign;
            )*
            smatch!(@p $body; $($assign),*$name; $stream,$cnt + 1;$($rest)*)
        } else {
            (None,false)
        }}
    };*/
    (@p $body: expr; $($assign: ident),*; $stream: expr,$cnt: expr; $name : ident = $p: expr => $($rest:tt)*) => {
        {let $name = $p;
        /*$(
            let $assign = $assign;
        )**/
        smatch!(@p $body; $name; $stream,$cnt + 1;$($rest)*)
        }
    };

    (@p $body: expr; $($assign: ident),*;$stream: expr,$cnt: expr;) => {
        {$stream.junk($cnt);
        (Some($body),true)
        }
    };

    (match ($s: expr) {
        $(
            [ $($p: tt)* ] => $body: expr
        ),*
    }) => {
        loop {
        $(
            let res =  smatch!(@p $body; $s,0; -> $($p)*);
            if let (Some(val),true) = res {
                break $crate::StreamResult::Ok(val);
            }
        )*
        break $crate::StreamResult::Err($crate::StreamError::EmptyStream)
        }
    };

}