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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::char as cchar;
use nom::combinator::{map, opt};
use nom::sequence::{delimited, pair, preceded, separated_pair, terminated, tuple};
use nom::IResult;

use crate::basic::{Identifier, Literal, Separator};
use crate::Parser;

// FieldType       ::=  Identifier | BaseType | ContainerType
// BaseType        ::=  'bool' | 'byte' | 'i8' | 'i16' | 'i32' | 'i64' | 'double' | 'string' | 'binary'
// ContainerType   ::=  MapType | SetType | ListType
// MapType         ::=  'map' CppType? '<' FieldType ',' FieldType '>'
// SetType         ::=  'set' CppType? '<' FieldType '>'
// ListType        ::=  'list' '<' FieldType '>' CppType?
// CppType         ::=  'cpp_type' Literal
// Note: CppType is not fully supported in out impl.
#[derive(Debug, Clone, PartialEq)]
pub enum FieldType<'a> {
    Identifier(Identifier<'a>),
    Bool,
    Byte,
    I8,
    I16,
    I32,
    I64,
    Double,
    String,
    Binary,
    Map(Box<FieldType<'a>>, Box<FieldType<'a>>),
    Set(Box<FieldType<'a>>),
    List(Box<FieldType<'a>>),
}

impl<'a> FieldType<'a> {
    pub fn parse_base_type(input: &'a str) -> IResult<&'a str, Self> {
        alt((
            map(tag("bool"), |_| Self::Bool),
            map(tag("byte"), |_| Self::Byte),
            map(tag("i8"), |_| Self::I8),
            map(tag("i16"), |_| Self::I16),
            map(tag("i32"), |_| Self::I32),
            map(tag("i64"), |_| Self::I64),
            map(tag("double"), |_| Self::Double),
            map(tag("string"), |_| Self::String),
            map(tag("binary"), |_| Self::Binary),
        ))(input)
    }

    pub fn parse_container_type(input: &'a str) -> IResult<&'a str, Self> {
        alt((
            map(
                preceded(
                    tuple((
                        tag("map"),
                        opt(Separator::parse),
                        opt(terminated(CppType::parse, opt(Separator::parse))),
                    )),
                    delimited(
                        pair(cchar('<'), opt(Separator::parse)),
                        separated_pair(
                            FieldType::parse,
                            tuple((opt(Separator::parse), cchar(','), opt(Separator::parse))),
                            FieldType::parse,
                        ),
                        pair(opt(Separator::parse), cchar('>')),
                    ),
                ),
                |(k, v)| Self::Map(Box::new(k), Box::new(v)),
            ),
            map(
                preceded(
                    tuple((
                        tag("set"),
                        opt(Separator::parse),
                        opt(terminated(CppType::parse, opt(Separator::parse))),
                    )),
                    delimited(
                        pair(cchar('<'), opt(Separator::parse)),
                        FieldType::parse,
                        pair(opt(Separator::parse), cchar('>')),
                    ),
                ),
                |v| Self::Set(Box::new(v)),
            ),
            map(
                delimited(
                    pair(tag("list"), opt(Separator::parse)),
                    delimited(
                        pair(cchar('<'), opt(Separator::parse)),
                        FieldType::parse,
                        pair(opt(Separator::parse), cchar('>')),
                    ),
                    opt(pair(opt(Separator::parse), CppType::parse)),
                ),
                |v| Self::List(Box::new(v)),
            ),
            map(Identifier::parse, Self::Identifier),
        ))(input)
    }

    pub fn parse_identifier_type(input: &'a str) -> IResult<&'a str, Self> {
        map(Identifier::parse, Self::Identifier)(input)
    }
}

impl<'a> Parser<'a> for FieldType<'a> {
    fn parse(input: &'a str) -> IResult<&'a str, Self> {
        alt((
            Self::parse_base_type,
            Self::parse_container_type,
            Self::parse_identifier_type,
        ))(input)
    }
}

// CppType         ::=  'cpp_type' Literal
#[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)]
pub struct CppType<'a>(Literal<'a>);

impl<'a> Parser<'a> for CppType<'a> {
    fn parse(input: &'a str) -> IResult<&'a str, Self> {
        map(
            preceded(tag("cpp_type"), preceded(Separator::parse, Literal::parse)),
            Self,
        )(input)
    }
}

#[cfg(test)]
mod test {
    use crate::utils::*;

    use super::*;

    #[test]
    fn test_cpp_type() {
        assert_list_eq_with_f(
            vec!["cpp_type \"MINI-LUST\"", "cpp_type 'ihciah'"],
            vec![Literal::from("MINI-LUST"), Literal::from("ihciah")],
            CppType::parse,
            CppType,
        );
    }

    #[test]
    fn test_field_type() {
        assert_list_eq_with_f(
            vec!["bool", "i16"],
            vec![FieldType::Bool, FieldType::I16],
            FieldType::parse,
            |x| x,
        );
        assert_eq!(
            FieldType::parse("map <bool, bool>").unwrap().1,
            FieldType::Map(Box::new(FieldType::Bool), Box::new(FieldType::Bool))
        );
        assert_eq!(
            FieldType::parse("map<bool,bool>").unwrap().1,
            FieldType::Map(Box::new(FieldType::Bool), Box::new(FieldType::Bool))
        );
        assert_eq!(
            FieldType::parse("set <bool>").unwrap().1,
            FieldType::Set(Box::new(FieldType::Bool))
        );
        assert_eq!(
            FieldType::parse("set<bool>").unwrap().1,
            FieldType::Set(Box::new(FieldType::Bool))
        );
        assert_eq!(
            FieldType::parse("list <bool>").unwrap().1,
            FieldType::List(Box::new(FieldType::Bool))
        );
        assert_eq!(
            FieldType::parse("list<bool>").unwrap().1,
            FieldType::List(Box::new(FieldType::Bool))
        );
        assert_eq!(
            FieldType::parse("ihc_iah").unwrap().1,
            FieldType::Identifier(Identifier::from("ihc_iah"))
        );
    }
}