controlmap_parser/
parser.rs

1//! # ControlMap Parser
2//!
3//! `controlmap_parser` is a minimal parser for controlmap.txt files.
4//!
5//! # EBNF
6//! ```EBNF
7//! <file> ::= <line>*
8//!
9//! <line> ::= <comment-line>
10//!          | <event-line>
11//!          | <blank-line>
12//!
13//! <comment-line> ::= "//" <string>
14//!
15//! <event-line> ::= <event-name> "\t"+ <keyboard-id> "\t"+ <mouse-id> "\t"+ <gamepad-id> "\t"+ <remap-key> "\t"+ <remap-mouse> "\t"+ <remap-gamepad> "\t"+ [<event-binary-flag>] <new-line>
16//!
17//! <event-name> ::= <string>
18//!
19//! <keyboard-id> ::= <key-map>
20//!
21//! <mouse-id> ::= <key-map>
22//!
23//! <gamepad-id> ::= <key-map>
24//!
25//! <remap-key> ::= "1" | "0"
26//!
27//! <remap-mouse> ::= "1" | "0"
28//!
29//! <remap-gamepad> ::= "1" | "0"
30//!
31//! <event-binary-flag> ::= <hexadecimal>
32//!
33//! <blank-line> ::= <new-line>
34//!
35//! # KeyMap
36//! <key-map> ::= <key-alias> | <key-or> | <key-and> | <hexadecimal>
37//! <key-alias> ::= "!0," <event-name>
38//! <key-or> ::= <key-map> "," <key-map>
39//! <key-and> ::= <key-id> "+" <key-id>
40//! <key-id> ::= <hexadecimal>
41//!
42//! # primitives
43//!
44//! <new-line> ::= "\r"? "\n"
45//! <string> ::= Any valid string
46//!
47//! <hexadecimal> ::= "0x" (<hex-digit>)+
48//!
49//! <hex-digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"  |
50//!                   "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F"
51//! ```
52
53use core::fmt;
54use nom::{
55    branch::alt,
56    bytes::complete::{tag, take_while1},
57    character::complete::{char, hex_digit1, line_ending, multispace1, not_line_ending, space0},
58    combinator::{map, opt, recognize},
59    error::{context, ErrorKind, ParseError},
60    multi::{many0, many1},
61    sequence::preceded,
62    AsChar, InputTakeAtPosition,
63};
64
65// NOTE:
66// Must be String to deserialize. If it is borrowed, it can be serialized,
67// but the memory it owns is dropped during the deserialization process.
68
69/// KeyMap Kind
70#[derive(Debug, Clone, PartialEq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub enum KeyID {
73    /// e.g. `0x0009,0x000`
74    Or(Vec<KeyID>),
75    /// e.g. `0x0009+0x000`
76    And(Vec<KeyID>),
77    /// e.g. `0x0009`
78    One(String),
79    /// e.g. `!0,Right Attack/Block`
80    Alias(String),
81}
82
83impl fmt::Display for KeyID {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            KeyID::Or(keys) => {
87                let keys_str: Vec<String> = keys.iter().map(|key| key.to_string()).collect();
88                write!(f, "{}", keys_str.join("+"))
89            }
90            KeyID::And(keys) => {
91                let keys_str: Vec<String> = keys.iter().map(|key| key.to_string()).collect();
92                write!(f, "{}", keys_str.join(","))
93            }
94            KeyID::One(key) => write!(f, "{}", key),
95            KeyID::Alias(alias) => write!(f, "!0,{}", alias),
96        }
97    }
98}
99
100/// One line event
101///
102/// e.g. `PickNext        0x09    0x1     0xff    0       0       0`
103#[derive(Debug, Clone, PartialEq)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
105pub struct EventLine {
106    pub event_name: String,
107    pub keyboard_id: KeyID,
108    pub mouse_id: KeyID,
109    pub gamepad_id: KeyID,
110    pub remap_key: bool,
111    pub remap_mouse: bool,
112    pub remap_gamepad: bool,
113    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
114    pub event_binary_flag: Option<String>,
115}
116
117impl fmt::Display for EventLine {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(
120            f,
121            "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}",
122            self.event_name,
123            self.keyboard_id,
124            self.mouse_id,
125            self.gamepad_id,
126            self.remap_key as u8,
127            self.remap_mouse as u8,
128            self.remap_gamepad as u8,
129            self.event_binary_flag.as_deref().unwrap_or_default()
130        )
131    }
132}
133
134/// A line expression
135#[derive(Debug, Clone, PartialEq)]
136#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
137pub enum Line {
138    /// e.g. `// This is a comment`
139    Comment(String),
140    /// e.g. `PickNext        0x09    0x1     0xff    0       0       0`
141    EventLine(EventLine),
142    /// new line `\n`
143    BlankLine,
144}
145
146impl fmt::Display for Line {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        match self {
149            Line::Comment(comment) => writeln!(f, "// {}", comment.trim()),
150            Line::EventLine(event_line) => writeln!(f, "{}", event_line),
151            Line::BlankLine => writeln!(f),
152        }
153    }
154}
155
156/// one or more tab space
157fn tab_space1<T, E: ParseError<T>>(input: T) -> nom::IResult<T, T, E>
158where
159    T: InputTakeAtPosition,
160    <T as InputTakeAtPosition>::Item: AsChar + Clone,
161{
162    input.split_at_position1_complete(|item| item.as_char() != '\t', ErrorKind::Space)
163}
164
165type IResult<I, O> = nom::IResult<I, O, nom::error::VerboseError<I>>;
166
167fn parse_hex(input: &str) -> IResult<&str, &str> {
168    let (input, _) = space0(input)?;
169    recognize(preceded(tag("0x"), hex_digit1))(input)
170}
171
172fn parse_flag(input: &str) -> IResult<&str, bool> {
173    let (input, flag) = alt((char('0'), char('1')))(input)?;
174    let flag = match flag {
175        '0' => false,
176        '1' => true,
177        _ => unreachable!(),
178    };
179    Ok((input, flag))
180}
181
182fn parse_key_and(input: &str) -> IResult<&str, KeyID> {
183    let (input, key) = parse_key_one(input)?;
184    let (input, ref mut keys) = many1(preceded(tag("+"), parse_key_one))(input)?;
185    let mut res = vec![key];
186    res.append(keys);
187    Ok((input, KeyID::And(res)))
188}
189
190fn parse_key_or(input: &str) -> IResult<&str, KeyID> {
191    let mut parse_and1 = alt((parse_key_and, parse_key_one));
192
193    let (input, key) = parse_and1(input)?;
194    let (input, ref mut keys) = many1(preceded(tag(","), parse_and1))(input)?;
195    let mut res = vec![key];
196    res.append(keys);
197    Ok((input, KeyID::Or(res)))
198}
199
200fn parse_key_one(input: &str) -> IResult<&str, KeyID> {
201    map(parse_hex, |key| KeyID::One(key.into()))(input)
202}
203
204fn parse_key_alias(input: &str) -> IResult<&str, KeyID> {
205    map(preceded(tag("!0,"), parse_event_name), |key| {
206        KeyID::Alias(key.into())
207    })(input)
208}
209
210fn parse_key_id(input: &str) -> IResult<&str, KeyID> {
211    alt((parse_key_alias, parse_key_or, parse_key_and, parse_key_one))(input)
212}
213
214fn parse_event_name(input: &str) -> IResult<&str, &str> {
215    context(
216        "Expected ident. non tab any string",
217        take_while1(|c: char| c != '\t'),
218    )(input)
219}
220
221fn parse_comment_line(input: &str) -> IResult<&str, Line> {
222    let (input, comment) = preceded(tag("//"), not_line_ending)(input)?;
223    let (input, _) = opt(line_ending)(input)?;
224    Ok((input, Line::Comment(comment.into())))
225}
226
227fn parse_event_line(input: &str) -> IResult<&str, Line> {
228    let (input, event_name) = parse_event_name(input)?;
229    let (input, _) = tab_space1(input)?;
230    let (input, keyboard_id) = parse_key_id(input)?;
231    let (input, _) = tab_space1(input)?;
232    let (input, mouse_id) = parse_key_id(input)?;
233    let (input, _) = tab_space1(input)?;
234    let (input, gamepad_id) = parse_key_id(input)?;
235    let (input, _) = tab_space1(input)?;
236    let (input, remap_key) = parse_flag(input)?;
237    let (input, _) = tab_space1(input)?;
238    let (input, remap_mouse) = parse_flag(input)?;
239    let (input, _) = tab_space1(input)?;
240    let (input, remap_gamepad) = parse_flag(input)?;
241    let (input, event_binary_flag) = opt(preceded(tab_space1, parse_hex))(input)?;
242    let (input, _) = space0(input)?; // ' ' or \t
243    let (input, _) = preceded(opt(tag("\r")), opt(tag("\n")))(input)?;
244
245    Ok((
246        input,
247        Line::EventLine(EventLine {
248            event_name: event_name.into(),
249            keyboard_id,
250            mouse_id,
251            gamepad_id,
252            remap_key,
253            remap_mouse,
254            remap_gamepad,
255            event_binary_flag: event_binary_flag.map(|event| event.into()),
256        }),
257    ))
258}
259
260fn parse_blank_line(input: &str) -> IResult<&str, Line> {
261    let (input, _) = multispace1(input)?;
262    Ok((input, Line::BlankLine))
263}
264
265/// parse controlmap.txt
266///
267/// # Examples
268/// ```
269/// use pretty_assertions::assert_eq;
270/// use controlmap_parser::parser::{control_map_parser, EventLine, Line, KeyID};
271///
272/// let input = r#"
273/// // Main Gameplay
274/// Forward				0x11		0xff	0xff			1	1	0	0x801
275/// Back				0x1f		0xff	0xff			1	1	0	0x801
276/// // Menu Mode
277/// Accept		!0,Activate	0xff	0x2000	0	0	0	0x8
278/// "#;
279/// let actual = control_map_parser(input);
280///
281/// let expected = Ok((
282///     "",
283///     vec![
284///         Line::BlankLine,
285///         Line::Comment(" Main Gameplay".into()),
286///         Line::EventLine(EventLine {
287///             event_name: "Forward".into(),
288///             keyboard_id: KeyID::One("0x11".into()),
289///             mouse_id: KeyID::One("0xff".into()),
290///             gamepad_id: KeyID::One("0xff".into()),
291///             remap_key: true,
292///             remap_mouse: true,
293///             remap_gamepad: false,
294///             event_binary_flag: Some("0x801".into()),
295///         }),
296///         Line::EventLine(EventLine {
297///             event_name: "Back".into(),
298///             keyboard_id: KeyID::One("0x1f".into()),
299///             mouse_id: KeyID::One("0xff".into()),
300///             gamepad_id: KeyID::One("0xff".into()),
301///             remap_key: true,
302///             remap_mouse: true,
303///             remap_gamepad: false,
304///             event_binary_flag: Some("0x801".into()),
305///         }),
306///         Line::Comment(" Menu Mode".into()),
307///         Line::EventLine(EventLine {
308///             event_name: "Accept".into(),
309///             keyboard_id: KeyID::Alias("Activate".into()),
310///             mouse_id: KeyID::One("0xff".into()),
311///             gamepad_id: KeyID::One("0x2000".into()),
312///             remap_key: false,
313///             remap_mouse: false,
314///             remap_gamepad: false,
315///             event_binary_flag: Some("0x8".into()),
316///         }),
317///     ],
318/// ));
319/// assert_eq!(actual, expected);
320/// ```
321pub fn control_map_parser(input: &str) -> IResult<&str, Vec<Line>> {
322    let parse_line = alt((parse_blank_line, parse_comment_line, parse_event_line));
323    many0(parse_line)(input)
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329    use pretty_assertions::assert_eq;
330
331    #[test]
332    fn test_parse_hex() {
333        let input = "0x1234";
334        let expected_output = Ok(("", "0x1234"));
335        let result = parse_hex(input);
336        assert_eq!(result, expected_output);
337    }
338
339    #[test]
340    fn test_parse_flag() {
341        let input = "0";
342        let expected_output = Ok(("", false));
343        let result = parse_flag(input);
344        assert_eq!(result, expected_output);
345    }
346
347    #[test]
348    fn test_parse_key_id_one() {
349        let input = "0x1234";
350        let expected_output = Ok(("", KeyID::One("0x1234".into())));
351        let result = parse_key_id(input);
352        assert_eq!(result, expected_output);
353    }
354
355    #[test]
356    fn test_parse_key_id_and() {
357        let input = "0x1234+0x5678+0x9abc";
358        let expected_output = Ok((
359            "",
360            KeyID::And(vec![
361                KeyID::One("0x1234".into()),
362                KeyID::One("0x5678".into()),
363                KeyID::One("0x9abc".into()),
364            ]),
365        ));
366        let result = parse_key_id(input);
367        assert_eq!(result, expected_output);
368    }
369
370    #[test]
371    fn test_parse_key_id_or() {
372        let input = "0x2a+0x0f,0x36+0x0,0x1234,0x5678,0x9abc";
373        let expected_output = Ok((
374            "",
375            KeyID::Or(vec![
376                KeyID::And(vec![KeyID::One("0x2a".into()), KeyID::One("0x0f".into())]),
377                KeyID::And(vec![KeyID::One("0x36".into()), KeyID::One("0x0".into())]),
378                KeyID::One("0x1234".into()),
379                KeyID::One("0x5678".into()),
380                KeyID::One("0x9abc".into()),
381            ]),
382        ));
383        let result = parse_key_id(input);
384        assert_eq!(result, expected_output)
385    }
386
387    #[test]
388    fn test_parse_comment_line() {
389        let input = "// This is a comment\n";
390        let expected_output = Ok(("", Line::Comment(" This is a comment".into())));
391        let result = parse_comment_line(input);
392        assert_eq!(result, expected_output);
393    }
394}