gobble/
macros.rs

1//! The macros primarily exist to make creating zero size parsers easier.
2//! Without putting them in macros "&'static str" and "chars" can act as parsers,
3//! but they have a size, and when combined they can become bigger.
4//! If however all the parsers you combine have zero size, then the final resulting parser
5//! will also be zero size and therefor much easier to construct
6//!
7
8/// Makes zero sized parsers based on the expression given and potentially the return type given.
9
10/// ```rust
11/// use gobble::*;
12/// parser!{
13///     (Cat->String),
14///     "cat".plus(),
15/// }
16/// assert_eq!(Cat.parse_s("ctar"),Ok("cta".to_string()));
17/// ```
18#[macro_export]
19macro_rules! parser {
20    ($id:ident,$x:expr) => {
21        parser!(($id->&'static str) $x);
22    };
23    ($($doc:literal $(,)?)? ($id:ident -> $ot:ty) $(,)? $x:expr $(,)?) => {
24        parser!($($doc)? ($id->$ot) $x, Expected::Str(stringify!($id)));
25    };
26    ($id:ident,$x:expr,$exp:expr) => {
27        parser!(($id->&'static str) $x, $exp);
28    };
29    ($($doc:literal $(,)?)? ($id:ident -> $ot:ty) $(,)? $x:expr,$exp:expr $(,)?) => {
30        $(#[doc=$doc])?
31        #[derive(Copy, Clone)]
32        pub struct $id;
33        impl Parser for $id {
34            type Out = $ot;
35            ///Parse run the main parser
36            fn parse<'a>(&self, it: &LCChars<'a>) -> ParseRes<'a, Self::Out> {
37                let name_e = it.err_p(self);
38                match (&$x).parse(it){
39                    Ok(v)=> Ok(v),
40                    Err(e)=> match (e.index,name_e.index) {
41                        (Some(ei),Some(ii)) if (ii == ei) => it.err_rp(self),
42                        _=>Err(e.join(name_e)),
43                    }
44                }
45            }
46            ///The expected return type
47            fn expected(&self) -> Expected {
48                $exp
49            }
50        }
51    };
52}
53
54#[macro_export]
55macro_rules! parser_as {
56    (($ot:ty),(($id:ident->$res:expr) $(,)? $main:expr,$exp:expr $(,)?) ) => {
57        parser! {($id->$ot) ,$main.map(|_|$res),$exp}
58    };
59    (($ot:ty),(($id:ident->$res:expr) $(,)? $main:expr $(,)?) ) => {
60        parser! {($id->$ot) ,$main.map(|_|$res)}
61    };
62    (($ot:ty),($id:ident, $main:expr)) => {
63        parser! { ($id->$ot) $main}
64    };
65}
66
67#[macro_export]
68macro_rules! as_id {
69    ((($id:ident->$_x:expr) $($_t:tt)*) ) => {
70        $id
71    };
72    (($id:ident $($_t:tt)*) ) => {
73        $id
74    };
75}
76
77/// ```rust
78///
79/// use gobble::*;
80/// mod scoper{
81///     // had to make a new scope for the doc test but it shouldn't be needed
82///     // from outer crates
83///     use gobble::*;
84///     //declare the enum
85///     #[derive(Clone, PartialEq, Debug)]
86///     pub enum Oper {
87///         Add,
88///         Sub,
89///         Div,
90///         Mul,
91///         Var(String),
92///     }
93///     
94///     enum_parser! { (OPER,oper,Oper) =>
95///         ((ADD->Oper::Add) '+'),
96///         ((SUB->Oper::Sub) '-'),
97///         ((DIV->Oper::Div) '/'),
98///         ((MUL->Oper::Mul) '*'),
99///         (VAR , Alpha.plus().map(|s|Oper::Var(s))),
100///     }
101/// }
102/// use scoper::*;
103///
104/// let v = star(scoper::OPER).parse_s("-cat").unwrap();
105/// assert_eq!( v, vec![ Oper::Sub, Oper::Var("cat".to_string()) ]);
106///
107/// let v2 = star(or!(oper::ADD, oper::SUB)).parse_s("-+-hello").unwrap();
108/// assert_eq!(v2, vec![Oper::Sub, Oper::Add, Oper::Sub]);
109///
110///
111/// ```
112#[macro_export]
113macro_rules! enum_parser{
114    ( ($name:ident,$mod:ident,$ot:ty)=>$($mbit:tt),* $(,)?) =>{
115        pub mod $mod{
116            use $crate::*;
117            use super::*;
118            $( parser_as!{($ot),$mbit})*
119            parser!{ ($name->$ot) ( or!{ $(as_id!{$mbit}),*} )}
120        }
121        pub use $mod::$name;
122    }
123}
124
125#[macro_export]
126macro_rules! char_bool {
127    ($id:ident,$x:expr) => {
128        char_bool!($id, $x, Expected::CharIn(stringify!($id)));
129    };
130    ($id:ident,$x:expr,$s:literal) => {
131        char_bool!($id, $x, Expected::CharIn($s));
132    };
133    ($id:ident,$x:expr,$exp:expr) => {
134        #[derive(Copy, Clone)]
135        pub struct $id;
136        impl CharBool for $id {
137            fn char_bool(&self, c: char) -> bool {
138                (&$x).char_bool(c)
139            }
140            fn expected(&self) -> Expected {
141                $exp
142            }
143        }
144    };
145}
146
147#[macro_export]
148macro_rules! char_bools {
149    ( $( ($id:ident,$x:expr) ),*) => {$(char_bool!($id,$x);)*};
150}
151
152/// a macro replacement for numbered or statements.
153/// ```rust
154/// use gobble::*;
155/// assert_eq!(or!("cat","dog","car",).parse_s("catdogman "),Ok("cat"));
156/// ```
157#[macro_export]
158macro_rules! or{
159    ($s:expr,$($x:expr),* $(,)?) => { $s$(.or($x))*;};
160}
161
162#[macro_export]
163macro_rules! or_ig{
164    ($s:expr,$($x:expr),* $(,)?) => { $s.ig()$(.or($x.ig()))*;};
165}
166
167#[cfg(test)]
168mod test {
169
170    fn size_of<T: Sized>(_t: &T) -> usize {
171        std::mem::size_of::<T>()
172    }
173
174    use crate::*;
175    parser!(DOG, "dog");
176    parser!(CAR, "car");
177    parser!(CAT, "cat");
178
179    parser!((GROW->Vec<&'static str>) star(or(CAT, DOG)));
180
181    #[test]
182    pub fn parser_makes_parser() {
183        assert_eq!(DOG.parse_s("dog   "), Ok("dog"));
184        assert_eq!(CAT.parse_s("cat    "), Ok("cat"));
185        assert_eq!(
186            GROW.parse_s("catdogcatcatno"),
187            Ok(vec!["cat", "dog", "cat", "cat"])
188        );
189    }
190
191    char_bool!(HOT, "hot");
192    char_bool!(MNUM, |c| c >= '0' && c <= '9');
193
194    #[test]
195    pub fn charbool_macro_makes_parser() {
196        use Expected::*;
197        let p = (HOT, MNUM);
198        assert_eq!(std::mem::size_of::<(HOT, MNUM)>(), 0);
199        assert_eq!(p.plus().parse_s("09h3f"), Ok("09h3".to_string()));
200        assert_eq!(p.expected(), OneOf(vec![CharIn("HOT"), CharIn("MNUM")]));
201        assert_eq!(size_of(&p), 0);
202    }
203    #[derive(Clone, PartialEq, Debug)]
204    pub enum Oper {
205        Add,
206        Sub,
207        Div,
208        Mul,
209        Var(String),
210    }
211
212    enum_parser! { (OPER,oper,Oper) =>
213        ((ADD->Oper::Add) '+'),
214        ((SUB->Oper::Sub) '-'),
215        ((DIV->Oper::Div) '/'),
216        ((MUL->Oper::Mul) '*'),
217        (VAR , Alpha.plus().map(|s|Oper::Var(s))),
218    }
219
220    #[test]
221    fn test_enum_group_make_parser() {
222        let v = star(OPER).parse_s("-cat").unwrap();
223        assert_eq!(v, vec![Oper::Sub, Oper::Var("cat".to_string())]);
224
225        let v2 = star(or!(oper::ADD, oper::SUB)).parse_s("-+-hello").unwrap();
226        assert_eq!(v2, vec![Oper::Sub, Oper::Add, Oper::Sub]);
227    }
228}