rmk_config/
layout.rs

1use std::collections::HashMap;
2
3use pest::Parser;
4use pest_derive::Parser;
5
6use crate::{KeyInfo, KeyboardTomlConfig, LayoutConfig};
7
8// Pest parser using the grammar files
9#[derive(Parser)]
10#[grammar = "keymap.pest"]
11struct ConfigParser;
12
13// Max alias resolution depth to prevent infinite loops
14const MAX_ALIAS_RESOLUTION_DEPTH: usize = 10;
15
16impl KeyboardTomlConfig {
17    /// Layout is a mandatory field in toml, so we mainly check the sizes
18    pub fn get_layout_config(&self) -> Result<(LayoutConfig, Vec<Vec<KeyInfo>>), String> {
19        let aliases = self.aliases.clone().unwrap_or_default();
20        let layers = self.layer.clone().unwrap_or_default();
21        let mut layout = self.layout.clone().expect("layout config is required");
22
23        // Temporarily allow both matrix_map and keymap to be set and append the obsolete layout.keymap based layer configurations
24        // to the new [[layer]] based layer configurations in the resulting LayoutConfig
25
26        // Check alias keys for whitespace
27        for key in aliases.keys() {
28            if key.chars().any(char::is_whitespace) {
29                return Err(format!(
30                    "keyboard.toml: Alias key '{}' must not contain whitespace characters",
31                    key
32                ));
33            }
34        }
35
36        let mut final_layers = Vec::<Vec<Vec<String>>>::new();
37        let mut key_info: Vec<Vec<KeyInfo>> =
38            vec![vec![KeyInfo::default(); layout.cols as usize]; layout.rows as usize];
39        let mut sequence_to_grid: Option<Vec<(u8, u8)>> = None;
40        if let Some(matrix_map) = &layout.matrix_map {
41            // process matrix_map first to build mapping between the electronic grid and the configuration sequence of keys
42            let mut sequence_number = 0u32;
43            let mut grid_to_sequence: Vec<Vec<Option<u32>>> =
44                vec![vec![None; layout.cols as usize]; layout.rows as usize];
45            match Self::parse_matrix_map(matrix_map) {
46                Ok(info) => {
47                    let mut coords = Vec::<(u8, u8)>::new();
48                    for (row, col, hand) in &info {
49                        if *row >= layout.rows || *col >= layout.cols {
50                            return Err(format!(
51                                "keyboard.toml: Coordinate ({},{}) in `layout.matrix_map` is out of bounds: ([0..{}], [0..{}]) is the expected range",
52                                row,
53                                col,
54                                layout.rows - 1,
55                                layout.cols - 1
56                            ));
57                        }
58                        if grid_to_sequence[*row as usize][*col as usize].is_some() {
59                            return Err(format!(
60                                "keyboard.toml: Duplicate coordinate ({},{}) found in `layout.matrix_map`",
61                                row, col
62                            ));
63                        } else {
64                            // Separate coordinates from key info
65                            coords.push((*row, *col));
66                            grid_to_sequence[*row as usize][*col as usize] = Some(sequence_number);
67                            key_info[*row as usize][*col as usize] = KeyInfo { hand: *hand };
68                        }
69                        sequence_number += 1;
70                    }
71                    sequence_to_grid = Some(coords);
72                }
73                Err(parse_err) => {
74                    // Pest error already includes details about the invalid format
75                    return Err(format!("keyboard.toml: Error in `layout.matrix_map`: {}", parse_err));
76                }
77            }
78        } else if !layers.is_empty() {
79            return Err("layout.matrix_map is need to be defined to process [[layer]] based key maps".to_string());
80        }
81        if let Some(sequence_to_grid) = &sequence_to_grid {
82            // collect layer names first
83            let mut layer_names = HashMap::<String, u32>::new();
84            for (layer_number, layer) in layers.iter().enumerate() {
85                if let Some(name) = &layer.name {
86                    if layer_names.contains_key(name) {
87                        return Err(format!(
88                            "keyboard.toml: Duplicate layer name '{}' found in `layout.keymap`",
89                            name
90                        ));
91                    }
92                    layer_names.insert(name.clone(), layer_number as u32);
93                }
94            }
95            if layers.len() > layout.layers as usize {
96                return Err("keyboard.toml: Number of [[layer]] entries is larger than layout.layers".to_string());
97            }
98            // Parse each explicitly defined [[layer]] with pest into the final_layers vector
99            // using the previously defined sequence_to_grid mapping to fill in the
100            // grid shaped classic keymaps
101            let layer_names = layer_names;
102            for (layer_number, layer) in layers.iter().enumerate() {
103                // each layer should contain a sequence of keymap entries
104                // their number and order should match the number and order of the above parsed matrix map
105                match Self::keymap_parser(&layer.keys, &aliases, &layer_names) {
106                    Ok(key_action_sequence) => {
107                        let mut legacy_keymap =
108                            vec![vec!["No".to_string(); layout.cols as usize]; layout.rows as usize];
109                        for (sequence_number, key_action) in key_action_sequence.into_iter().enumerate() {
110                            if sequence_number >= sequence_to_grid.len() {
111                                return Err(format!(
112                                    "keyboard.toml: {} layer #{} contains too many entries (must match layout.matrix_map)",
113                                    &layer.name.clone().unwrap_or_default(),
114                                    layer_number
115                                ));
116                            }
117                            let (row, col) = sequence_to_grid[sequence_number];
118                            legacy_keymap[row as usize][col as usize] = key_action.clone();
119                        }
120                        final_layers.push(legacy_keymap);
121                    }
122                    Err(parse_err) => {
123                        return Err(format!("keyboard.toml: Error in `layout.keymap`: {}", parse_err));
124                    }
125                }
126            }
127        }
128        // Handle the deprecated `keymap` field if present
129        if let Some(keymap) = &mut layout.keymap {
130            final_layers.append(keymap);
131        }
132        // The required number of layers is less than what's set in keymap
133        // Fill the rest with empty keys
134        if final_layers.len() <= layout.layers as usize {
135            for _ in final_layers.len()..layout.layers as usize {
136                // Add 2D vector of empty keys
137                final_layers.push(vec![vec!["_".to_string(); layout.cols as usize]; layout.rows as usize]);
138            }
139        } else {
140            return Err(format!(
141                "keyboard.toml: The actual number of layers is larger than {} [layout.layers]: {} [[Layer]] entries + {} layers in layout.keymap",
142                layout.layers,
143                layers.len(),
144                layout.keymap.as_ref().map(|keymap| keymap.len()).unwrap_or_default()
145            ));
146        }
147        // Row
148        if final_layers.iter().any(|r| r.len() as u8 != layout.rows) {
149            return Err("keyboard.toml: Row number in keymap doesn't match with [layout.row]".to_string());
150        }
151        // Col
152        if final_layers
153            .iter()
154            .any(|r| r.iter().any(|c| c.len() as u8 != layout.cols))
155        {
156            return Err("keyboard.toml: Col number in keymap doesn't match with [layout.col]".to_string());
157        }
158
159        // Process encoder map
160        let mut encoder_map: Vec<Vec<[String; 2]>> = vec![];
161        for layer in &layers {
162            let mut encoders = layer.encoders.clone().unwrap_or_default();
163            for [cw, ccw] in &mut encoders {
164                *cw = Self::alias_resolver(cw, &aliases)?;
165                *ccw = Self::alias_resolver(ccw, &aliases)?;
166            }
167            encoder_map.push(encoders);
168        }
169        if let Some(deprecated_encoder_map) = &mut layout.encoder_map {
170            encoder_map.append(deprecated_encoder_map);
171        }
172
173        Ok((
174            LayoutConfig {
175                rows: layout.rows,
176                cols: layout.cols,
177                layers: layout.layers,
178                keymap: final_layers,
179                encoder_map,
180            },
181            key_info,
182        ))
183    }
184
185    /// Parses and validates a matrix_map string using Pest.
186    /// Ensures the string contains only valid coordinates and whitespace.
187    fn parse_matrix_map(matrix_map: &str) -> Result<Vec<(u8, u8, char)>, String> {
188        match ConfigParser::parse(Rule::matrix_map, matrix_map) {
189            Ok(pairs) => {
190                let mut key_info = Vec::new();
191                // The top-level pair is 'matrix_map'. We need to iterate its inner content.
192                for pair in pairs {
193                    // Should only be one pair matching Rule::matrix_map
194                    if pair.as_rule() == Rule::matrix_map {
195                        for inner_pair in pair.into_inner() {
196                            match inner_pair.as_rule() {
197                                Rule::keypos_info => {
198                                    let mut items = inner_pair.into_inner(); // Should contain two 'number' pairs
199
200                                    let row_str = items.next().ok_or("Missing row coordinate")?.as_str();
201                                    let col_str = items.next().ok_or("Missing col coordinate")?.as_str();
202
203                                    let row = row_str
204                                        .parse::<u8>()
205                                        .map_err(|e| format!("Failed to parse row '{}': {}", row_str, e))?;
206                                    let col = col_str
207                                        .parse::<u8>()
208                                        .map_err(|e| format!("Failed to parse col '{}': {}", col_str, e))?;
209
210                                    let mut hand = 'C'; // C for center (not specified)
211
212                                    for part in items {
213                                        match part.as_rule() {
214                                            Rule::left_hand => hand = 'L',
215                                            Rule::right_hand => hand = 'R',
216                                            _ => {}
217                                        }
218                                    }
219
220                                    key_info.push((row, col, hand));
221                                }
222                                Rule::EOI | Rule::WHITESPACE => {
223                                    // Ignore End Of Input marker
224                                }
225                                _ => {
226                                    // This case should not be reached
227                                    return Err(format!(
228                                        "Unexpected rule encountered during layout.matrix_map processing: {:?}",
229                                        inner_pair.as_rule()
230                                    ));
231                                }
232                            }
233                        }
234                    }
235                }
236                Ok(key_info)
237            }
238            Err(e) => Err(format!("Invalid layout.matrix_map format: {}", e)),
239        }
240    }
241
242    fn alias_resolver(keys: &str, aliases: &HashMap<String, String>) -> Result<String, String> {
243        let mut current_keys = keys.to_string();
244
245        let mut iterations = 0;
246
247        loop {
248            let mut next_keys = String::with_capacity(current_keys.capacity());
249            let mut made_replacement = false;
250            let mut last_index = 0; // Keep track of where we are in current_keys
251
252            while let Some(at_index) = current_keys[last_index..].find('@') {
253                let start_index = last_index + at_index;
254
255                // Append the text before the '@'
256                next_keys.push_str(&current_keys[last_index..start_index]);
257
258                // Check if it's a valid alias start (@ followed by a non whitespace)
259                if let Some(first_char) = current_keys.as_bytes().get(start_index + 1) {
260                    if !first_char.is_ascii_whitespace() {
261                        // Find the end of the alias identifier
262                        let mut end_index = start_index + 2;
263                        while let Some(c) = current_keys.as_bytes().get(end_index) {
264                            if c.is_ascii_whitespace() {
265                                break;
266                            } else {
267                                end_index += 1;
268                            }
269                        }
270
271                        // Extract the alias key (except the starting '@')
272                        let alias_key = &current_keys[start_index + 1..end_index];
273
274                        // Look up and replace
275                        match aliases.get(alias_key) {
276                            Some(value) => {
277                                next_keys.push_str(value);
278                                made_replacement = true;
279                            }
280                            None => return Err(format!("Undefined alias: {}", alias_key)),
281                        }
282                        last_index = end_index; // Move past the processed alias
283                    } else {
284                        // Not a valid alias start, treat '@' literally
285                        next_keys.push('@');
286                        last_index = start_index + 1;
287                    }
288                } else {
289                    // '@' was the last character, treat it literally
290                    next_keys.push('@');
291                    last_index = start_index + 1;
292                    break; // No more characters after '@'
293                }
294            }
295
296            // Append any remaining part of the string after the last '@' or if no '@' was found
297            next_keys.push_str(&current_keys[last_index..]);
298
299            // Check for termination conditions
300            iterations += 1;
301            if iterations >= MAX_ALIAS_RESOLUTION_DEPTH {
302                return Err(format!(
303                    "Alias resolution exceeded maximum depth ({}), potential infinite loop detected in '{}'",
304                    MAX_ALIAS_RESOLUTION_DEPTH, keys
305                )); // Show original keys for context
306            }
307
308            if !made_replacement {
309                break; // No more replacements needed
310            }
311
312            // Prepare for the next iteration
313            current_keys = next_keys;
314        }
315
316        Ok(current_keys)
317    }
318
319    fn layer_name_resolver(
320        prefix: &str,
321        pair: pest::iterators::Pair<Rule>,
322        layer_names: &HashMap<String, u32>,
323    ) -> Result<String, String> {
324        let mut action = prefix.to_string() + "(";
325
326        for inner_pair in pair.into_inner() {
327            match inner_pair.as_rule() {
328                //the first argument is the layer name or layer number
329                Rule::layer_name => {
330                    // Check if the layer name is valid
331                    let layer_name = inner_pair.as_str().to_string();
332                    if let Some(layer_number) = layer_names.get(&layer_name) {
333                        action += layer_number.to_string().as_str();
334                    } else {
335                        return Err(format!("Invalid layer name: {}", layer_name));
336                    }
337                }
338                Rule::layer_number => {
339                    action += inner_pair.as_str();
340                }
341                _ => {
342                    // the second argument is not processed, just forwarded
343                    action += ", ";
344                    action += inner_pair.as_str();
345                }
346            }
347        }
348        action += ")";
349
350        Ok(action)
351    }
352
353    fn keymap_parser(
354        layer_keys: &str,
355        aliases: &HashMap<String, String>,
356        layer_names: &HashMap<String, u32>,
357    ) -> Result<Vec<String>, String> {
358        //resolve aliases first
359        let layer_keys = Self::alias_resolver(layer_keys, aliases)?;
360
361        let mut key_action_sequence = Vec::new();
362
363        // Parse the keymap using Pest
364        match ConfigParser::parse(Rule::key_map, &layer_keys) {
365            Ok(pairs) => {
366                // The top-level pair is 'key_map'. We need to iterate its inner content.
367                for pair in pairs {
368                    // Should only be one pair matching Rule::key_map
369                    if pair.as_rule() == Rule::key_map {
370                        for inner_pair in pair.into_inner() {
371                            match inner_pair.as_rule() {
372                                Rule::no_action => {
373                                    let action = inner_pair.as_str().to_string();
374                                    key_action_sequence.push(action);
375                                }
376
377                                Rule::transparent_action => {
378                                    let action = inner_pair.as_str().to_string();
379                                    key_action_sequence.push(action);
380                                }
381
382                                Rule::simple_keycode => {
383                                    let action = inner_pair.as_str().to_string();
384                                    key_action_sequence.push(action);
385                                }
386
387                                Rule::shifted_action => {
388                                    let action = inner_pair.as_str().to_string();
389                                    key_action_sequence.push(action);
390                                }
391
392                                Rule::osm_action => {
393                                    let action = inner_pair.as_str().to_string();
394                                    key_action_sequence.push(action);
395                                }
396
397                                Rule::wm_action => {
398                                    let action = inner_pair.as_str().to_string();
399                                    key_action_sequence.push(action);
400                                }
401
402                                //layer actions:
403                                Rule::df_action => {
404                                    key_action_sequence.push(Self::layer_name_resolver("DF", inner_pair, layer_names)?);
405                                }
406                                Rule::mo_action => {
407                                    key_action_sequence.push(Self::layer_name_resolver("MO", inner_pair, layer_names)?);
408                                }
409                                Rule::lm_action => {
410                                    key_action_sequence.push(Self::layer_name_resolver("LM", inner_pair, layer_names)?);
411                                }
412                                Rule::lt_action => {
413                                    key_action_sequence.push(Self::layer_name_resolver("LT", inner_pair, layer_names)?);
414                                    //"LT(".to_owned() + &Self::layer_name_resolver(inner_pair, layer_names)? + ")");
415                                }
416                                Rule::osl_action => {
417                                    key_action_sequence.push(Self::layer_name_resolver(
418                                        "OSL",
419                                        inner_pair,
420                                        layer_names,
421                                    )?);
422                                }
423                                Rule::tt_action => {
424                                    key_action_sequence.push(Self::layer_name_resolver("TT", inner_pair, layer_names)?);
425                                }
426                                Rule::tg_action => {
427                                    key_action_sequence.push(Self::layer_name_resolver("TG", inner_pair, layer_names)?);
428                                }
429                                Rule::to_action => {
430                                    key_action_sequence.push(Self::layer_name_resolver("TO", inner_pair, layer_names)?);
431                                }
432
433                                // tap-hold actions:
434                                Rule::mt_action => {
435                                    let action = inner_pair.as_str().to_string();
436                                    key_action_sequence.push(action);
437                                }
438                                Rule::th_action => {
439                                    let action = inner_pair.as_str().to_string();
440                                    key_action_sequence.push(action);
441                                }
442
443                                Rule::morse_action => {
444                                    let action = inner_pair.as_str().to_string();
445                                    key_action_sequence.push(action);
446                                }
447
448                                Rule::trigger_macro_action => {
449                                    let action = inner_pair.as_str().to_string();
450                                    key_action_sequence.push(action);
451                                }
452
453                                Rule::EOI | Rule::WHITESPACE => {
454                                    // Ignore End of input marker
455                                }
456                                _ => {
457                                    // This case should not be reached
458                                    panic!(
459                                        "Unexpected rule encountered during layer.keys processing:{:?}",
460                                        inner_pair.as_rule()
461                                    );
462                                }
463                            }
464                        }
465                    }
466                }
467            }
468            Err(e) => {
469                panic!("Invalid keymap format: {}", e);
470            }
471        }
472
473        Ok(key_action_sequence)
474    }
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480
481    #[test]
482    fn test_no_action_parsing() {
483        // Test "No" followed by whitespace
484        let test_cases = vec![
485            ("No ", vec!["No"]),
486            ("No\n", vec!["No"]),
487            ("No\t", vec!["No"]),
488            ("No  A", vec!["No", "A"]),
489            ("A No B", vec!["A", "No", "B"]),
490            ("No No No", vec!["No", "No", "No"]),
491        ];
492
493        for (input, expected) in test_cases {
494            let result = ConfigParser::parse(Rule::key_map, input);
495            assert!(result.is_ok(), "Failed to parse: {}", input);
496
497            let mut actions = Vec::new();
498            for pair in result.unwrap() {
499                if pair.as_rule() == Rule::key_map {
500                    for inner_pair in pair.into_inner() {
501                        match inner_pair.as_rule() {
502                            Rule::no_action | Rule::simple_keycode => {
503                                actions.push(inner_pair.as_str().to_string());
504                            }
505                            Rule::EOI | Rule::WHITESPACE => {}
506                            _ => {}
507                        }
508                    }
509                }
510            }
511
512            assert_eq!(actions, expected, "Input: {}", input);
513        }
514    }
515
516    #[test]
517    fn test_no_vs_no_prefixed_keycodes() {
518        // Test that "No" is parsed as no_action but "NoUsSlash" is parsed as simple_keycode
519        let test_cases = vec![
520            ("No", Rule::no_action),
521            ("NoUsSlash", Rule::simple_keycode),
522            ("NonUsSlash", Rule::simple_keycode),
523            ("NoReturn", Rule::simple_keycode),
524            ("NoBrake", Rule::simple_keycode),
525        ];
526
527        for (input, expected_rule) in test_cases {
528            let result = ConfigParser::parse(Rule::key_map, input);
529            assert!(result.is_ok(), "Failed to parse: {}", input);
530
531            let mut found_rule = None;
532            for pair in result.unwrap() {
533                if pair.as_rule() == Rule::key_map {
534                    for inner_pair in pair.into_inner() {
535                        match inner_pair.as_rule() {
536                            Rule::no_action | Rule::simple_keycode => {
537                                found_rule = Some(inner_pair.as_rule());
538                            }
539                            _ => {}
540                        }
541                    }
542                }
543            }
544
545            assert_eq!(
546                found_rule,
547                Some(expected_rule),
548                "Input: {} should be parsed as {:?}",
549                input,
550                expected_rule
551            );
552        }
553    }
554
555    #[test]
556    fn test_keymap_parser_with_no_actions() {
557        let aliases = HashMap::new();
558        let layer_names = HashMap::new();
559
560        // Test parsing a keymap string with "No" actions
561        let keymap = "A B No C No NoUsSlash NonUsSlash D No";
562        let result = KeyboardTomlConfig::keymap_parser(keymap, &aliases, &layer_names);
563
564        assert!(result.is_ok());
565        let actions = result.unwrap();
566        assert_eq!(
567            actions,
568            vec!["A", "B", "No", "C", "No", "NoUsSlash", "NonUsSlash", "D", "No"]
569        );
570    }
571
572    #[test]
573    fn test_morse_action_parsing() {
574        let aliases = HashMap::new();
575        let layer_names = HashMap::new();
576
577        // Test parsing a keymap string with TD actions
578        let keymap = "A TD(0) B TD(1) C TD(255)";
579        let result = KeyboardTomlConfig::keymap_parser(keymap, &aliases, &layer_names);
580
581        assert!(result.is_ok());
582        let actions = result.unwrap();
583        assert_eq!(actions, vec!["A", "TD(0)", "B", "TD(1)", "C", "TD(255)"]);
584    }
585
586    #[test]
587    fn test_macro_trigger_action_parsing() {
588        let aliases = std::collections::HashMap::new();
589        let layer_names = std::collections::HashMap::new();
590
591        // Test parsing a keymap string with macro trigger actions
592        let keymap = "A Macro(0) B MACRO(1) C macro(255)";
593        let result = KeyboardTomlConfig::keymap_parser(keymap, &aliases, &layer_names);
594
595        assert!(result.is_ok());
596        let actions = result.unwrap();
597        assert_eq!(actions, vec!["A", "Macro(0)", "B", "MACRO(1)", "C", "macro(255)"]);
598    }
599
600    #[test]
601    fn test_morse_action_grammar() {
602        // Test that TD actions are parsed correctly by the grammar
603        let test_cases = vec![
604            ("TD(0)", Rule::morse_action),
605            ("TD(1)", Rule::morse_action),
606            ("TD(255)", Rule::morse_action),
607            ("td(0)", Rule::morse_action), // Case insensitive
608            ("td(1)", Rule::morse_action),
609            ("MORSE(0)", Rule::morse_action),
610            ("MORSE(1)", Rule::morse_action),
611            ("MORSE(255)", Rule::morse_action),
612            ("Morse(0)", Rule::morse_action), // Case insensitive
613            ("morse(1)", Rule::morse_action),
614        ];
615
616        for (input, expected_rule) in test_cases {
617            let result = ConfigParser::parse(Rule::key_map, input);
618            assert!(result.is_ok(), "Failed to parse: {}", input);
619
620            let mut found_rule = None;
621            for pair in result.unwrap() {
622                if pair.as_rule() == Rule::key_map {
623                    for inner_pair in pair.into_inner() {
624                        match inner_pair.as_rule() {
625                            Rule::morse_action => {
626                                found_rule = Some(inner_pair.as_rule());
627                            }
628                            _ => {}
629                        }
630                    }
631                }
632            }
633
634            assert_eq!(
635                found_rule,
636                Some(expected_rule),
637                "Input: {} should be parsed as {:?}",
638                input,
639                expected_rule
640            );
641        }
642    }
643
644    #[test]
645    fn test_macro_grammar() {
646        // Test that macro actions are parsed correctly by the grammar
647        let test_cases = vec![
648            ("Macro(0)", Rule::trigger_macro_action),
649            ("Macro(1)", Rule::trigger_macro_action),
650            ("Macro(255)", Rule::trigger_macro_action),
651            ("MACRO(0)", Rule::trigger_macro_action), // Case insensitive
652            ("MACRO(1)", Rule::trigger_macro_action),
653            ("macro(0)", Rule::trigger_macro_action), // Case insensitive
654            ("macro(1)", Rule::trigger_macro_action),
655            ("macro(255)", Rule::trigger_macro_action),
656        ];
657
658        for (input, expected_rule) in test_cases {
659            let result = ConfigParser::parse(Rule::key_map, input);
660            assert!(result.is_ok(), "Failed to parse: {}", input);
661
662            let mut found_rule = None;
663            for pair in result.unwrap() {
664                if pair.as_rule() == Rule::key_map {
665                    for inner_pair in pair.into_inner() {
666                        match inner_pair.as_rule() {
667                            Rule::trigger_macro_action => {
668                                found_rule = Some(inner_pair.as_rule());
669                            }
670                            _ => {}
671                        }
672                    }
673                }
674            }
675
676            assert_eq!(
677                found_rule,
678                Some(expected_rule),
679                "Input: {} should be parsed as {:?}",
680                input,
681                expected_rule
682            );
683        }
684    }
685}