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}