gilrs/mapping/
parser.rs

1// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7use std::error::Error as StdError;
8use std::fmt::{self, Display};
9
10use uuid::Uuid;
11
12use crate::ev::{Axis, AxisOrBtn, Button};
13
14// Must be sorted!
15static AXES_SDL: [&str; 31] = [
16    "a",
17    "b",
18    "back",
19    "c",
20    "dpdown",
21    "dpleft",
22    "dpright",
23    "dpup",
24    "guide",
25    "leftshoulder",
26    "leftstick",
27    "lefttrigger",
28    "leftx",
29    "lefty",
30    "leftz",
31    "misc1",
32    "paddle1",
33    "paddle2",
34    "paddle3",
35    "paddle4",
36    "rightshoulder",
37    "rightstick",
38    "righttrigger",
39    "rightx",
40    "righty",
41    "rightz",
42    "start",
43    "touchpad",
44    "x",
45    "y",
46    "z",
47];
48static AXES: [AxisOrBtn; 31] = [
49    AxisOrBtn::Btn(Button::South),
50    AxisOrBtn::Btn(Button::East),
51    AxisOrBtn::Btn(Button::Select),
52    AxisOrBtn::Btn(Button::C),
53    AxisOrBtn::Btn(Button::DPadDown),
54    AxisOrBtn::Btn(Button::DPadLeft),
55    AxisOrBtn::Btn(Button::DPadRight),
56    AxisOrBtn::Btn(Button::DPadUp),
57    AxisOrBtn::Btn(Button::Mode),
58    AxisOrBtn::Btn(Button::LeftTrigger),
59    AxisOrBtn::Btn(Button::LeftThumb),
60    AxisOrBtn::Btn(Button::LeftTrigger2),
61    AxisOrBtn::Axis(Axis::LeftStickX),
62    AxisOrBtn::Axis(Axis::LeftStickY),
63    AxisOrBtn::Axis(Axis::LeftZ),
64    AxisOrBtn::Btn(Button::Unknown),
65    AxisOrBtn::Btn(Button::Unknown),
66    AxisOrBtn::Btn(Button::Unknown),
67    AxisOrBtn::Btn(Button::Unknown),
68    AxisOrBtn::Btn(Button::Unknown),
69    AxisOrBtn::Btn(Button::RightTrigger),
70    AxisOrBtn::Btn(Button::RightThumb),
71    AxisOrBtn::Btn(Button::RightTrigger2),
72    AxisOrBtn::Axis(Axis::RightStickX),
73    AxisOrBtn::Axis(Axis::RightStickY),
74    AxisOrBtn::Axis(Axis::RightZ),
75    AxisOrBtn::Btn(Button::Start),
76    AxisOrBtn::Btn(Button::Unknown),
77    AxisOrBtn::Btn(Button::West),
78    AxisOrBtn::Btn(Button::North),
79    AxisOrBtn::Btn(Button::Z),
80];
81
82pub struct Parser<'a> {
83    data: &'a str,
84    pos: usize,
85    state: State,
86}
87
88impl<'a> Parser<'a> {
89    pub fn new(mapping: &'a str) -> Self {
90        Parser {
91            data: mapping,
92            pos: 0,
93            state: State::Uuid,
94        }
95    }
96
97    pub fn next_token(&mut self) -> Option<Result<Token<'_>, Error>> {
98        if self.pos >= self.data.len() {
99            None
100        } else {
101            Some(match self.state {
102                State::Uuid => self.parse_uuid(),
103                State::Name => self.parse_name(),
104                State::KeyVal => self.parse_key_val(),
105                State::Invalid => Err(Error::new(ErrorKind::InvalidParserState, self.pos)),
106            })
107        }
108    }
109
110    fn parse_uuid(&mut self) -> Result<Token<'_>, Error> {
111        let next_comma = self.next_comma_or_end();
112        let uuid_field = &self.data[self.pos..next_comma];
113        let uuid = if uuid_field == "xinput" {
114            Ok(Token::Uuid(Uuid::nil()))
115        } else {
116            Uuid::parse_str(uuid_field)
117                .map(Token::Uuid)
118                .map_err(|_| Error::new(ErrorKind::InvalidGuid, self.pos))
119        };
120
121        if uuid.is_err() {
122            self.state = State::Invalid;
123        } else if next_comma == self.data.len() {
124            self.state = State::Invalid;
125
126            return Err(Error::new(ErrorKind::UnexpectedEnd, self.pos));
127        } else {
128            self.state = State::Name;
129            self.pos = next_comma + 1;
130        }
131
132        uuid
133    }
134
135    fn parse_name(&mut self) -> Result<Token<'_>, Error> {
136        let next_comma = self.next_comma_or_end();
137        let name = &self.data[self.pos..next_comma];
138
139        self.state = State::KeyVal;
140        self.pos = next_comma + 1;
141
142        Ok(Token::Name(name))
143    }
144
145    fn parse_key_val(&mut self) -> Result<Token<'_>, Error> {
146        let next_comma = self.next_comma_or_end();
147        let pair = &self.data[self.pos..next_comma];
148        let pos = self.pos;
149        self.pos = next_comma + 1;
150
151        let mut split = pair.split(':');
152        let key = split
153            .next()
154            .ok_or_else(|| Error::new(ErrorKind::InvalidKeyValPair, pos))?;
155        let value = split
156            .next()
157            .ok_or_else(|| Error::new(ErrorKind::InvalidKeyValPair, pos))?;
158
159        if split.next().is_some() {
160            return Err(Error::new(ErrorKind::InvalidKeyValPair, pos));
161        }
162
163        if value.is_empty() {
164            return Err(Error::new(ErrorKind::EmptyValue, pos));
165        }
166
167        if key == "platform" {
168            return Ok(Token::Platform(value));
169        }
170
171        let mut input = AxisRange::Full;
172        let mut output = AxisRange::Full;
173        let mut inverted = false;
174        let mut is_axis = false;
175
176        let key = match key.get(0..1) {
177            Some("+") => {
178                output = AxisRange::UpperHalf;
179                &key[1..]
180            }
181            Some("-") => {
182                output = AxisRange::LowerHalf;
183                &key[1..]
184            }
185            _ => key,
186        };
187
188        let from = match value.get(0..1) {
189            Some("+") if value.get(1..2) == Some("a") => {
190                is_axis = true;
191                input = AxisRange::UpperHalf;
192
193                if value.get((value.len() - 1)..) == Some("~") {
194                    inverted = true;
195
196                    &value[2..(value.len() - 1)]
197                } else {
198                    &value[2..]
199                }
200            }
201            Some("-") if value.get(1..2) == Some("a") => {
202                is_axis = true;
203                input = AxisRange::LowerHalf;
204
205                if value.get((value.len() - 1)..) == Some("~") {
206                    inverted = true;
207
208                    &value[2..(value.len() - 1)]
209                } else {
210                    &value[2..]
211                }
212            }
213            Some("a") => {
214                is_axis = true;
215
216                if value.get((value.len() - 1)..) == Some("~") {
217                    inverted = true;
218
219                    &value[1..(value.len() - 1)]
220                } else {
221                    &value[1..]
222                }
223            }
224            Some("b") => &value[1..],
225            Some("h") => {
226                let dot_idx = value
227                    .find('.')
228                    .ok_or_else(|| Error::new(ErrorKind::InvalidValue, pos))?;
229                let hat = value[1..dot_idx]
230                    .parse()
231                    .map_err(|_| Error::new(ErrorKind::InvalidValue, pos + 1))?;
232                let direction = value
233                    .get((dot_idx + 1)..)
234                    .and_then(|s| s.parse().ok())
235                    .ok_or_else(|| Error::new(ErrorKind::InvalidValue, pos + dot_idx + 1))?;
236
237                let idx = AXES_SDL
238                    .binary_search(&key)
239                    .map_err(|_| Error::new(ErrorKind::UnknownButton, pos))?;
240
241                return Ok(Token::HatMapping {
242                    hat,
243                    direction,
244                    to: AXES[idx],
245                    output,
246                });
247            }
248            _ => return Err(Error::new(ErrorKind::InvalidValue, pos)),
249        }
250        .parse::<u16>()
251        .map_err(|_| Error::new(ErrorKind::InvalidValue, pos))?;
252
253        if is_axis {
254            let idx = AXES_SDL
255                .binary_search(&key)
256                .map_err(|_| Error::new(ErrorKind::UnknownAxis, pos))?;
257
258            Ok(Token::AxisMapping {
259                from,
260                to: AXES[idx],
261                input,
262                output,
263                inverted,
264            })
265        } else {
266            let idx = AXES_SDL
267                .binary_search(&key)
268                .map_err(|_| Error::new(ErrorKind::UnknownButton, pos))?;
269
270            Ok(Token::ButtonMapping {
271                from,
272                to: AXES[idx],
273                output,
274            })
275        }
276    }
277
278    fn next_comma_or_end(&self) -> usize {
279        self.data[self.pos..]
280            .find(',')
281            .map(|x| x + self.pos)
282            .unwrap_or_else(|| self.data.len())
283    }
284}
285
286#[derive(Debug)]
287pub enum Token<'a> {
288    Uuid(Uuid),
289    Platform(&'a str),
290    Name(&'a str),
291    #[allow(dead_code)]
292    AxisMapping {
293        from: u16,
294        to: AxisOrBtn,
295        input: AxisRange,
296        output: AxisRange,
297        inverted: bool,
298    },
299    ButtonMapping {
300        from: u16,
301        to: AxisOrBtn,
302        #[allow(dead_code)]
303        output: AxisRange,
304    },
305    // This is just SDL representation, we will convert this to axis mapping later
306    HatMapping {
307        hat: u16,
308        // ?
309        direction: u16,
310        to: AxisOrBtn,
311        #[allow(dead_code)]
312        output: AxisRange,
313    },
314}
315
316#[repr(u8)]
317#[derive(Debug)]
318pub enum AxisRange {
319    LowerHalf,
320    UpperHalf,
321    Full,
322}
323
324#[derive(Copy, Clone, Eq, PartialEq)]
325enum State {
326    Uuid,
327    Name,
328    KeyVal,
329    Invalid,
330}
331
332#[derive(Debug, Clone, PartialEq, Eq)]
333pub struct Error {
334    pub(crate) position: usize,
335    kind: ErrorKind,
336}
337
338impl Error {
339    pub fn new(kind: ErrorKind, position: usize) -> Self {
340        Error { position, kind }
341    }
342
343    pub fn kind(&self) -> &ErrorKind {
344        &self.kind
345    }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq)]
349#[non_exhaustive]
350pub enum ErrorKind {
351    InvalidGuid,
352    InvalidKeyValPair,
353    InvalidValue,
354    EmptyValue,
355    UnknownAxis,
356    UnknownButton,
357    InvalidParserState,
358    UnexpectedEnd,
359}
360
361impl StdError for Error {}
362
363impl Display for Error {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        let s = match self.kind {
366            ErrorKind::InvalidGuid => "GUID is invalid",
367            ErrorKind::InvalidKeyValPair => "expected key value pair",
368            ErrorKind::InvalidValue => "value is not valid",
369            ErrorKind::EmptyValue => "value is empty",
370            ErrorKind::UnknownAxis => "invalid axis name",
371            ErrorKind::UnknownButton => "invalid button name",
372            ErrorKind::InvalidParserState => "attempt to parse after unrecoverable error",
373            ErrorKind::UnexpectedEnd => "mapping does not have all required fields",
374        };
375
376        f.write_fmt(format_args!("{} at {}", s, self.position))
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use crate::mapping::parser::{ErrorKind, Parser};
383    use crate::utils::PATH_SEPARATOR;
384
385    #[test]
386    fn test_all_sdl_mappings_for_parse_errors() {
387        let included_mappings = include_str!(concat!(
388            env!("OUT_DIR"),
389            PATH_SEPARATOR!(),
390            "gamecontrollerdb.txt"
391        ))
392        .lines();
393
394        let mut errors = 0;
395        let mut index = 0;
396        for line in included_mappings {
397            let mut parser = Parser::new(line);
398
399            while let Some(token) = parser.next_token() {
400                if let Err(ref e) = token {
401                    if e.kind() != &ErrorKind::EmptyValue {
402                        errors += 1;
403                        println!("{:?}", e);
404                        println!(
405                            "{}: {} (...) {}\n",
406                            index,
407                            line.chars().take(50).collect::<String>(),
408                            line.chars().skip(e.position).take(15).collect::<String>()
409                        );
410
411                        if e.kind() == &ErrorKind::InvalidParserState {
412                            break;
413                        }
414                    }
415                }
416                index += 1;
417            }
418        }
419        assert_eq!(errors, 0);
420    }
421}