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
185
186
187
use std::str::FromStr;

#[cfg(test)]
use crate::idl::common::assert_parse;
use crate::idl::common::{parse_identifier, Span};
use nom::{
    branch::alt,
    bytes::complete::{escaped_transform, is_a, is_not, tag},
    character::complete::{char, digit1, one_of},
    combinator::{cut, map, map_res, opt},
    error::context,
    sequence::{pair, preceded, separated_pair, terminated},
    IResult,
};

#[derive(Debug, PartialEq)]
pub enum Value {
    Boolean(bool),
    Integer(i64),
    Float(f64),
    Range(Option<i64>, Option<i64>),
    String(String),
    Identifier(String),
}

pub fn parse_boolean(input: Span) -> IResult<Span, bool> {
    alt((map(tag("false"), |_| false), map(tag("true"), |_| true)))(input)
}

pub fn parse_string(input: Span) -> IResult<Span, String> {
    context(
        "string",
        preceded(
            char('\"'),
            cut(terminated(
                map(
                    escaped_transform(
                        is_not("\\\"\n"),
                        '\\',
                        alt((
                            map(tag("\\"), |_| "\\"),
                            map(tag("\""), |_| "\""),
                            map(tag("n"), |_| "\n"),
                        )),
                    ),
                    String::from,
                ),
                char('\"'),
            )),
        ),
    )(input)
}

#[test]
fn test_parse_value_string() {
    assert_parse(
        parse_value(Span::new("\"hello\"")),
        Value::String("hello".to_string()),
    );
    assert_parse(
        parse_value(Span::new("\"hello world\"")),
        Value::String("hello world".to_string()),
    );
    assert_parse(
        parse_value(Span::new("\"hello\\nworld\"")),
        Value::String("hello\nworld".to_string()),
    );
    assert_parse(
        parse_value(Span::new("\"hello \\\"world\\\"\"")),
        Value::String("hello \"world\"".to_string()),
    );
    assert_parse(
        parse_value(Span::new("\"backspace\\\\\"")),
        Value::String("backspace\\".to_string()),
    );
}

pub fn parse_integer_dec(input: Span) -> IResult<Span, i64> {
    map_res(pair(opt(one_of("+-")), digit1), |(sign, number)| {
        format!("{}{}", sign.unwrap_or('+'), number).parse::<i64>()
    })(input)
}

pub fn parse_integer_hex(input: Span) -> IResult<Span, i64> {
    map_res(
        pair(
            opt(one_of("+-")),
            preceded(alt((tag("0x"), tag("0X"))), is_a("1234567890ABCDEFabcdef")),
        ),
        |(sign, number)| {
            i64::from_str_radix(format!("{}{}", sign.unwrap_or('+'), number).as_str(), 16)
        },
    )(input)
}

pub fn parse_integer(input: Span) -> IResult<Span, i64> {
    alt((parse_integer_hex, parse_integer_dec))(input)
}

pub fn parse_float(input: Span) -> IResult<Span, f64> {
    context(
        "float",
        map_res(
            pair(opt(one_of("+-")), separated_pair(digit1, char('.'), digit1)),
            |(sign, (a, b))| f64::from_str(format!("{}{}.{}", sign.unwrap_or('+'), a, b).as_str()),
        ),
    )(input)
}

pub fn parse_range(input: Span) -> IResult<Span, (Option<i64>, Option<i64>)> {
    context(
        "range",
        separated_pair(opt(parse_integer), tag(".."), opt(parse_integer)),
    )(input)
}

pub fn parse_value(input: Span) -> IResult<Span, Value> {
    alt((
        map(parse_boolean, Value::Boolean),
        map(parse_range, |(min, max)| Value::Range(min, max)),
        map(parse_float, Value::Float),
        map(parse_integer, Value::Integer),
        map(parse_string, Value::String),
        map(parse_identifier, Value::Identifier),
    ))(input)
}

#[test]
fn test_parse_value_boolean() {
    assert_parse(parse_value(Span::new("true")), Value::Boolean(true));
    assert_parse(parse_value(Span::new("false")), Value::Boolean(false));
}

#[test]
fn test_parse_value_integer() {
    assert_parse(parse_value(Span::new("1337")), Value::Integer(1337));
    assert_parse(parse_value(Span::new("-42")), Value::Integer(-42));
    assert_parse(
        parse_value(Span::new("9223372036854775807")),
        Value::Integer(9223372036854775807),
    );
    assert_parse(
        parse_value(Span::new("-9223372036854775808")),
        Value::Integer(-9223372036854775808),
    );
    assert_parse(parse_value(Span::new("0xFF")), Value::Integer(0xFF));
    assert_parse(parse_value(Span::new("-0xFF")), Value::Integer(-0xFF));
}

#[test]
fn test_parse_value_integer_out_of_range() {
    use nom::error::ErrorKind;
    assert_eq!(
        parse_value(Span::new("9223372036854775808")),
        Err(nom::Err::Error(nom::error::Error {
            input: Span::new("9223372036854775808"),
            code: ErrorKind::TakeWhile1
        }))
    );
    assert_eq!(
        parse_value(Span::new("-9223372036854775809")),
        Err(nom::Err::Error(nom::error::Error {
            input: Span::new("-9223372036854775809"),
            code: ErrorKind::TakeWhile1
        }))
    );
}

#[test]
fn test_parse_value_float() {
    assert_parse(parse_value(Span::new("1337.0")), Value::Float(1337f64));
    assert_parse(parse_value(Span::new("13.37")), Value::Float(13.37f64));
    assert_parse(parse_value(Span::new("+13.37")), Value::Float(13.37f64));
    assert_parse(parse_value(Span::new("-13.37")), Value::Float(-13.37f64));
}

#[test]
fn test_parse_value_range() {
    assert_parse(
        parse_value(Span::new("0..1337")),
        Value::Range(Some(0), Some(1337)),
    );
    assert_parse(
        parse_value(Span::new("0..0xFF")),
        Value::Range(Some(0), Some(0xFF)),
    );
}