1use crate::parser::common::{
6 compound_identifier, condition_value, many_until_eof, map_err, ws, BindParserError,
7 CompoundIdentifier, NomSpan, Value,
8};
9use nom::bytes::complete::tag;
10use nom::sequence::separated_pair;
11use nom::{IResult, Parser};
12use std::str::FromStr;
13
14#[derive(Debug, Clone, PartialEq)]
15pub struct DeviceSpecification {
16 pub properties: Vec<Property>,
17}
18
19#[derive(Debug, Clone, PartialEq)]
20pub struct Property {
21 pub key: CompoundIdentifier,
22 pub value: Value,
23}
24
25impl FromStr for DeviceSpecification {
26 type Err = BindParserError;
27
28 fn from_str(input: &str) -> Result<Self, Self::Err> {
29 match device_specification(NomSpan::new(input)) {
30 Ok((_, spec)) => Ok(spec),
31 Err(nom::Err::Error(e)) => Err(e),
32 Err(nom::Err::Failure(e)) => Err(e),
33 Err(nom::Err::Incomplete(_)) => {
34 unreachable!("Parser should never generate Incomplete errors")
35 }
36 }
37 }
38}
39
40impl DeviceSpecification {
41 pub fn new() -> Self {
42 DeviceSpecification { properties: Vec::new() }
43 }
44
45 pub fn add_property(&mut self, key: &str, value: &str) -> Result<(), BindParserError> {
46 match property_from_pair(NomSpan::new(key), NomSpan::new(value)) {
47 Ok((_, property)) => {
48 self.properties.push(property);
49 Ok(())
50 }
51 Err(nom::Err::Error(e)) => Err(e),
52 Err(nom::Err::Failure(e)) => Err(e),
53 Err(nom::Err::Incomplete(_)) => {
54 unreachable!("Parser should never generate Incomplete errors")
55 }
56 }
57 }
58}
59
60fn property_from_pair<'a, 'b>(
61 key: NomSpan<'a>,
62 value: NomSpan<'b>,
63) -> IResult<(NomSpan<'a>, NomSpan<'b>), Property, BindParserError> {
64 let (key_remaining, key) = compound_identifier(key)?;
65 if !key_remaining.fragment().is_empty() {
66 return Err(nom::Err::Error(BindParserError::Eof(key_remaining.fragment().to_string())));
67 }
68 let (value_remaining, value) = condition_value(value)?;
69 if !value_remaining.fragment().is_empty() {
70 return Err(nom::Err::Error(BindParserError::Eof(value_remaining.fragment().to_string())));
71 }
72 Ok(((key_remaining, value_remaining), Property { key, value }))
73}
74
75fn property(input: NomSpan) -> IResult<NomSpan, Property, BindParserError> {
76 let key = ws(compound_identifier);
77 let separator = ws(map_err(tag("="), BindParserError::Assignment));
78 let (input, (key, value)) = separated_pair(key, separator, condition_value).parse(input)?;
79 Ok((input, Property { key, value }))
80}
81
82fn device_specification(input: NomSpan) -> IResult<NomSpan, DeviceSpecification, BindParserError> {
83 let (input, properties) = many_until_eof(ws(property)).parse(input)?;
84 Ok((input, DeviceSpecification { properties }))
85}
86
87#[cfg(test)]
88mod test {
89 use super::*;
90 use crate::make_identifier;
91 use crate::parser::common::test::check_result;
92
93 mod properties {
94 use super::*;
95
96 #[test]
97 fn simple() {
98 check_result(
99 property(NomSpan::new("abc = 5")),
100 "",
101 Property { key: make_identifier!["abc"], value: Value::NumericLiteral(5) },
102 );
103 }
104
105 #[test]
106 fn simple_from_pair() {
107 assert_eq!(
108 property_from_pair(NomSpan::new("abc"), NomSpan::new("5")).unwrap().1,
109 Property { key: make_identifier!["abc"], value: Value::NumericLiteral(5) },
110 );
111 }
112
113 #[test]
114 fn invalid() {
115 assert_eq!(
116 property(NomSpan::new("abc 5")),
117 Err(nom::Err::Error(BindParserError::Assignment("5".to_string())))
118 );
119
120 assert_eq!(
121 property(NomSpan::new("abc =")),
122 Err(nom::Err::Error(BindParserError::ConditionValue("".to_string())))
123 );
124
125 assert_eq!(
126 property(NomSpan::new("= 5")),
127 Err(nom::Err::Error(BindParserError::Identifier("= 5".to_string())))
128 );
129 }
130
131 #[test]
132 fn invalid_from_pair() {
133 assert_eq!(
134 property_from_pair(NomSpan::new("abc def"), NomSpan::new("5")),
135 Err(nom::Err::Error(BindParserError::Eof(" def".to_string())))
136 );
137
138 assert_eq!(
139 property_from_pair(NomSpan::new("_abc"), NomSpan::new("5")),
140 Err(nom::Err::Error(BindParserError::Identifier("_abc".to_string())))
141 );
142
143 assert_eq!(
144 property_from_pair(NomSpan::new("abc"), NomSpan::new("5 42")),
145 Err(nom::Err::Error(BindParserError::Eof(" 42".to_string())))
146 );
147
148 assert_eq!(
149 property_from_pair(NomSpan::new("abc"), NomSpan::new("@")),
150 Err(nom::Err::Error(BindParserError::ConditionValue("@".to_string())))
151 );
152 }
153
154 #[test]
155 fn empty() {
156 assert_eq!(
157 property(NomSpan::new("")),
158 Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
159 );
160 }
161
162 #[test]
163 fn empty_from_pair() {
164 assert_eq!(
165 property_from_pair(NomSpan::new(""), NomSpan::new("5")),
166 Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
167 );
168
169 assert_eq!(
170 property_from_pair(NomSpan::new("abc"), NomSpan::new("")),
171 Err(nom::Err::Error(BindParserError::ConditionValue("".to_string())))
172 );
173 }
174 }
175
176 mod device_specifications {
177 use super::*;
178
179 #[test]
180 fn simple() {
181 check_result(
182 device_specification(NomSpan::new("abc = 5\nxyz = true")),
183 "",
184 DeviceSpecification {
185 properties: vec![
186 Property { key: make_identifier!["abc"], value: Value::NumericLiteral(5) },
187 Property { key: make_identifier!["xyz"], value: Value::BoolLiteral(true) },
188 ],
189 },
190 );
191 }
192
193 #[test]
194 fn empty() {
195 check_result(
196 device_specification(NomSpan::new("")),
197 "",
198 DeviceSpecification { properties: Vec::new() },
199 );
200 }
201 }
202}