webwire_cli/idl/
value.rs

1use std::str::FromStr;
2
3#[cfg(test)]
4use crate::idl::common::assert_parse;
5use crate::idl::common::{parse_identifier, Span};
6use nom::{
7    branch::alt,
8    bytes::complete::{escaped_transform, is_a, is_not, tag},
9    character::complete::{char, digit1, one_of},
10    combinator::{cut, map, map_res, opt},
11    error::context,
12    sequence::{pair, preceded, separated_pair, terminated},
13    IResult,
14};
15
16#[derive(Debug, PartialEq)]
17pub enum Value {
18    Boolean(bool),
19    Integer(i64),
20    Float(f64),
21    Range(Option<i64>, Option<i64>),
22    String(String),
23    Identifier(String),
24}
25
26pub fn parse_boolean(input: Span) -> IResult<Span, bool> {
27    alt((map(tag("false"), |_| false), map(tag("true"), |_| true)))(input)
28}
29
30pub fn parse_string(input: Span) -> IResult<Span, String> {
31    context(
32        "string",
33        preceded(
34            char('\"'),
35            cut(terminated(
36                map(
37                    escaped_transform(
38                        is_not("\\\"\n"),
39                        '\\',
40                        alt((
41                            map(tag("\\"), |_| "\\"),
42                            map(tag("\""), |_| "\""),
43                            map(tag("n"), |_| "\n"),
44                        )),
45                    ),
46                    String::from,
47                ),
48                char('\"'),
49            )),
50        ),
51    )(input)
52}
53
54#[test]
55fn test_parse_value_string() {
56    assert_parse(
57        parse_value(Span::new("\"hello\"")),
58        Value::String("hello".to_string()),
59    );
60    assert_parse(
61        parse_value(Span::new("\"hello world\"")),
62        Value::String("hello world".to_string()),
63    );
64    assert_parse(
65        parse_value(Span::new("\"hello\\nworld\"")),
66        Value::String("hello\nworld".to_string()),
67    );
68    assert_parse(
69        parse_value(Span::new("\"hello \\\"world\\\"\"")),
70        Value::String("hello \"world\"".to_string()),
71    );
72    assert_parse(
73        parse_value(Span::new("\"backspace\\\\\"")),
74        Value::String("backspace\\".to_string()),
75    );
76}
77
78pub fn parse_integer_dec(input: Span) -> IResult<Span, i64> {
79    map_res(pair(opt(one_of("+-")), digit1), |(sign, number)| {
80        format!("{}{}", sign.unwrap_or('+'), number).parse::<i64>()
81    })(input)
82}
83
84pub fn parse_integer_hex(input: Span) -> IResult<Span, i64> {
85    map_res(
86        pair(
87            opt(one_of("+-")),
88            preceded(alt((tag("0x"), tag("0X"))), is_a("1234567890ABCDEFabcdef")),
89        ),
90        |(sign, number)| {
91            i64::from_str_radix(format!("{}{}", sign.unwrap_or('+'), number).as_str(), 16)
92        },
93    )(input)
94}
95
96pub fn parse_integer(input: Span) -> IResult<Span, i64> {
97    alt((parse_integer_hex, parse_integer_dec))(input)
98}
99
100pub fn parse_float(input: Span) -> IResult<Span, f64> {
101    context(
102        "float",
103        map_res(
104            pair(opt(one_of("+-")), separated_pair(digit1, char('.'), digit1)),
105            |(sign, (a, b))| f64::from_str(format!("{}{}.{}", sign.unwrap_or('+'), a, b).as_str()),
106        ),
107    )(input)
108}
109
110pub fn parse_range(input: Span) -> IResult<Span, (Option<i64>, Option<i64>)> {
111    context(
112        "range",
113        separated_pair(opt(parse_integer), tag(".."), opt(parse_integer)),
114    )(input)
115}
116
117pub fn parse_value(input: Span) -> IResult<Span, Value> {
118    alt((
119        map(parse_boolean, Value::Boolean),
120        map(parse_range, |(min, max)| Value::Range(min, max)),
121        map(parse_float, Value::Float),
122        map(parse_integer, Value::Integer),
123        map(parse_string, Value::String),
124        map(parse_identifier, Value::Identifier),
125    ))(input)
126}
127
128#[test]
129fn test_parse_value_boolean() {
130    assert_parse(parse_value(Span::new("true")), Value::Boolean(true));
131    assert_parse(parse_value(Span::new("false")), Value::Boolean(false));
132}
133
134#[test]
135fn test_parse_value_integer() {
136    assert_parse(parse_value(Span::new("1337")), Value::Integer(1337));
137    assert_parse(parse_value(Span::new("-42")), Value::Integer(-42));
138    assert_parse(
139        parse_value(Span::new("9223372036854775807")),
140        Value::Integer(9223372036854775807),
141    );
142    assert_parse(
143        parse_value(Span::new("-9223372036854775808")),
144        Value::Integer(-9223372036854775808),
145    );
146    assert_parse(parse_value(Span::new("0xFF")), Value::Integer(0xFF));
147    assert_parse(parse_value(Span::new("-0xFF")), Value::Integer(-0xFF));
148}
149
150#[test]
151fn test_parse_value_integer_out_of_range() {
152    use nom::error::ErrorKind;
153    assert_eq!(
154        parse_value(Span::new("9223372036854775808")),
155        Err(nom::Err::Error(nom::error::Error {
156            input: Span::new("9223372036854775808"),
157            code: ErrorKind::TakeWhile1
158        }))
159    );
160    assert_eq!(
161        parse_value(Span::new("-9223372036854775809")),
162        Err(nom::Err::Error(nom::error::Error {
163            input: Span::new("-9223372036854775809"),
164            code: ErrorKind::TakeWhile1
165        }))
166    );
167}
168
169#[test]
170fn test_parse_value_float() {
171    assert_parse(parse_value(Span::new("1337.0")), Value::Float(1337f64));
172    assert_parse(parse_value(Span::new("13.37")), Value::Float(13.37f64));
173    assert_parse(parse_value(Span::new("+13.37")), Value::Float(13.37f64));
174    assert_parse(parse_value(Span::new("-13.37")), Value::Float(-13.37f64));
175}
176
177#[test]
178fn test_parse_value_range() {
179    assert_parse(
180        parse_value(Span::new("0..1337")),
181        Value::Range(Some(0), Some(1337)),
182    );
183    assert_parse(
184        parse_value(Span::new("0..0xFF")),
185        Value::Range(Some(0), Some(0xFF)),
186    );
187}