rmk_config/
layout.rs

1use pest::Parser;
2use pest_derive::Parser;
3
4use crate::{KeyboardTomlConfig, LayoutConfig};
5use std::collections::HashMap;
6
7// Pest parser using the grammar files
8#[derive(Parser)]
9#[grammar = "keymap.pest"]
10struct ConfigParser;
11
12// Max alias resolution depth to prevent infinite loops
13const MAX_ALIAS_RESOLUTION_DEPTH: usize = 10;
14
15impl KeyboardTomlConfig {
16    /// Layout is a mandatory field in toml, so we mainly check the sizes
17    pub fn get_layout_config(&self) -> Result<LayoutConfig, String> {
18        let aliases = self.aliases.clone().unwrap_or_default();
19        let layers = self.layer.clone().unwrap_or_default();
20        let mut layout = self.layout.clone().expect("layout config is required");
21        // temporarily allow both matrix_map and keymap to be set and append the obsolete layout.keymap based layer configurations
22        // to the new [[layer]] based layer configurations in the resulting LayoutConfig
23
24        // Check alias keys for whitespace
25        for key in aliases.keys() {
26            if key.chars().any(char::is_whitespace) {
27                return Err(format!(
28                    "keyboard.toml: Alias key '{}' must not contain whitespace characters",
29                    key
30                ));
31            }
32        }
33        let mut final_layers = Vec::<Vec<Vec<String>>>::new();
34        let mut sequence_to_grid: Option<Vec<(u8, u8)>> = None;
35        if let Some(matrix_map) = &layout.matrix_map {
36            // process matrix_map first to build mapping between the electronic grid and the configuration sequence of keys
37            let mut sequence_number = 0u32;
38            let mut grid_to_sequence: Vec<Vec<Option<u32>>> =
39                vec![vec![None; layout.cols as usize]; layout.rows as usize];
40            match Self::parse_matrix_map(matrix_map) {
41                Ok(coords) => {
42                    for (row, col) in &coords {
43                        if *row >= layout.rows || *col >= layout.cols {
44                            return Err(format!(
45                                "keyboard.toml: Coordinate ({},{}) in `layout.matrix_map` is out of bounds: ([0..{}], [0..{}]) is the expected range",
46                                row, col, layout.rows-1, layout.cols-1
47                            ));
48                        }
49                        if grid_to_sequence[*row as usize][*col as usize].is_some() {
50                            return Err(format!(
51                                "keyboard.toml: Duplicate coordinate ({},{}) found in `layout.matrix_map`",
52                                row, col
53                            ));
54                        } else {
55                            grid_to_sequence[*row as usize][*col as usize] = Some(sequence_number);
56                        }
57                        sequence_number += 1;
58                    }
59                    sequence_to_grid = Some(coords);
60                }
61                Err(parse_err) => {
62                    // Pest error already includes details about the invalid format
63                    return Err(format!("keyboard.toml: Error in `layout.matrix_map`: {}", parse_err));
64                }
65            }
66        } else if !layers.is_empty() {
67            return Err("layout.matrix_map is need to be defined to process [[layer]] based key maps".to_string());
68        }
69        if let Some(sequence_to_grid) = &sequence_to_grid {
70            // collect layer names first
71            let mut layer_names = HashMap::<String, u32>::new();
72            for (layer_number, layer) in layers.iter().enumerate() {
73                if let Some(name) = &layer.name {
74                    if layer_names.contains_key(name) {
75                        return Err(format!(
76                            "keyboard.toml: Duplicate layer name '{}' found in `layout.keymap`",
77                            name
78                        ));
79                    }
80                    layer_names.insert(name.clone(), layer_number as u32);
81                }
82            }
83            if layers.len() > layout.layers as usize {
84                return Err("keyboard.toml: Number of [[layer]] entries is larger than layout.layers".to_string());
85            }
86            // Parse each explicitly defined [[layer]] with pest into the final_layers vector
87            // using the previously defined sequence_to_grid mapping to fill in the
88            // grid shaped classic keymaps
89            let layer_names = layer_names;
90            for (layer_number, layer) in layers.iter().enumerate() {
91                // each layer should contain a sequence of keymap entries
92                // their number and order should match the number and order of the above parsed matrix map
93                match Self::keymap_parser(&layer.keys, &aliases, &layer_names) {
94                    Ok(key_action_sequence) => {
95                        let mut legacy_keymap =
96                            vec![vec!["No".to_string(); layout.cols as usize]; layout.rows as usize];
97                        for (sequence_number, key_action) in key_action_sequence.into_iter().enumerate() {
98                            if sequence_number >= sequence_to_grid.len() {
99                                return Err(format!(
100                                    "keyboard.toml: {} layer #{} contains too many entries (must match layout.matrix_map)", &layer.name.clone().unwrap_or_default(), layer_number));
101                            }
102                            let (row, col) = sequence_to_grid[sequence_number];
103                            legacy_keymap[row as usize][col as usize] = key_action.clone();
104                        }
105                        final_layers.push(legacy_keymap);
106                    }
107                    Err(parse_err) => {
108                        return Err(format!("keyboard.toml: Error in `layout.keymap`: {}", parse_err));
109                    }
110                }
111            }
112        }
113        // Handle the deprecated `keymap` field if present
114        if let Some(keymap) = &mut layout.keymap {
115            final_layers.append(keymap);
116        }
117        // The required number of layers is less than what's set in keymap
118        // Fill the rest with empty keys
119        if final_layers.len() <= layout.layers as usize {
120            for _ in final_layers.len()..layout.layers as usize {
121                // Add 2D vector of empty keys
122                final_layers.push(vec![vec!["_".to_string(); layout.cols as usize]; layout.rows as usize]);
123            }
124        } else {
125            return Err(format!(
126                "keyboard.toml: The actual number of layers is larger than {} [layout.layers]: {} [[Layer]] entries + {} layers in layout.keymap",
127                layout.layers, layers.len(), layout.keymap.as_ref().map(|keymap| keymap.len()).unwrap_or_default()
128            ));
129        }
130        // Row
131        if final_layers.iter().any(|r| r.len() as u8 != layout.rows) {
132            return Err("keyboard.toml: Row number in keymap doesn't match with [layout.row]".to_string());
133        }
134        // Col
135        if final_layers
136            .iter()
137            .any(|r| r.iter().any(|c| c.len() as u8 != layout.cols))
138        {
139            return Err("keyboard.toml: Col number in keymap doesn't match with [layout.col]".to_string());
140        }
141        Ok(LayoutConfig {
142            rows: layout.rows,
143            cols: layout.cols,
144            layers: layout.layers,
145            keymap: final_layers,
146        })
147    }
148
149    /// Parses and validates a matrix_map string using Pest.
150    /// Ensures the string contains only valid coordinates and whitespace.
151    fn parse_matrix_map(matrix_map: &str) -> Result<Vec<(u8, u8)>, String> {
152        match ConfigParser::parse(Rule::matrix_map, matrix_map) {
153            Ok(pairs) => {
154                let mut coordinates = Vec::new();
155                // The top-level pair is 'matrix_map'. We need to iterate its inner content.
156                for pair in pairs {
157                    // Should only be one pair matching Rule::matrix_map
158                    if pair.as_rule() == Rule::matrix_map {
159                        for inner_pair in pair.into_inner() {
160                            match inner_pair.as_rule() {
161                                Rule::coordinate => {
162                                    let mut coord_parts = inner_pair.into_inner(); // Should contain two 'number' pairs
163
164                                    let row_str = coord_parts.next().ok_or("Missing row coordinate")?.as_str();
165                                    let col_str = coord_parts.next().ok_or("Missing col coordinate")?.as_str();
166
167                                    let row = row_str
168                                        .parse::<u8>()
169                                        .map_err(|e| format!("Failed to parse row '{}': {}", row_str, e))?;
170                                    let col = col_str
171                                        .parse::<u8>()
172                                        .map_err(|e| format!("Failed to parse col '{}': {}", col_str, e))?;
173
174                                    coordinates.push((row, col));
175                                }
176                                Rule::EOI | Rule::WHITESPACE => {
177                                    // Ignore End Of Input marker
178                                }
179                                _ => {
180                                    // This case should not be reached
181                                    return Err(format!(
182                                        "Unexpected rule encountered during layout.matrix_map processing: {:?}",
183                                        inner_pair.as_rule()
184                                    ));
185                                }
186                            }
187                        }
188                    }
189                }
190                Ok(coordinates)
191            }
192            Err(e) => Err(format!("Invalid layout.matrix_map format: {}", e)),
193        }
194    }
195
196    fn alias_resolver(keys: &str, aliases: &HashMap<String, String>) -> Result<String, String> {
197        let mut current_keys = keys.to_string();
198
199        let mut iterations = 0;
200
201        loop {
202            let mut next_keys = String::with_capacity(current_keys.capacity());
203            let mut made_replacement = false;
204            let mut last_index = 0; // Keep track of where we are in current_keys
205
206            while let Some(at_index) = current_keys[last_index..].find('@') {
207                let start_index = last_index + at_index;
208
209                // Append the text before the '@'
210                next_keys.push_str(&current_keys[last_index..start_index]);
211
212                // Check if it's a valid alias start (@ followed by a non whitespace)
213                if let Some(first_char) = current_keys.as_bytes().get(start_index + 1) {
214                    if !first_char.is_ascii_whitespace() {
215                        // Find the end of the alias identifier
216                        let mut end_index = start_index + 2;
217                        while let Some(c) = current_keys.as_bytes().get(end_index) {
218                            if c.is_ascii_whitespace() {
219                                break;
220                            } else {
221                                end_index += 1;
222                            }
223                        }
224
225                        // Extract the alias key (except the starting '@')
226                        let alias_key = &current_keys[start_index + 1..end_index];
227
228                        // Look up and replace
229                        match aliases.get(alias_key) {
230                            Some(value) => {
231                                next_keys.push_str(value);
232                                made_replacement = true;
233                            }
234                            None => return Err(format!("Undefined alias: {}", alias_key)),
235                        }
236                        last_index = end_index; // Move past the processed alias
237                    } else {
238                        // Not a valid alias start, treat '@' literally
239                        next_keys.push('@');
240                        last_index = start_index + 1;
241                    }
242                } else {
243                    // '@' was the last character, treat it literally
244                    next_keys.push('@');
245                    last_index = start_index + 1;
246                    break; // No more characters after '@'
247                }
248            }
249
250            // Append any remaining part of the string after the last '@' or if no '@' was found
251            next_keys.push_str(&current_keys[last_index..]);
252
253            // Check for termination conditions
254            iterations += 1;
255            if iterations >= MAX_ALIAS_RESOLUTION_DEPTH {
256                return Err(format!(
257                    "Alias resolution exceeded maximum depth ({}), potential infinite loop detected in '{}'",
258                    MAX_ALIAS_RESOLUTION_DEPTH, keys
259                )); // Show original keys for context
260            }
261
262            if !made_replacement {
263                break; // No more replacements needed
264            }
265
266            // Prepare for the next iteration
267            current_keys = next_keys;
268        }
269
270        Ok(current_keys)
271    }
272
273    fn layer_name_resolver(
274        prefix: &str,
275        pair: pest::iterators::Pair<Rule>,
276        layer_names: &HashMap<String, u32>,
277    ) -> Result<String, String> {
278        let mut action = prefix.to_string() + "(";
279
280        for inner_pair in pair.into_inner() {
281            match inner_pair.as_rule() {
282                //the first argument is the layer name or layer number
283                Rule::layer_name => {
284                    // Check if the layer name is valid
285                    let layer_name = inner_pair.as_str().to_string();
286                    if let Some(layer_number) = layer_names.get(&layer_name) {
287                        action += layer_number.to_string().as_str();
288                    } else {
289                        return Err(format!("Invalid layer name: {}", layer_name));
290                    }
291                }
292                Rule::layer_number => {
293                    action += inner_pair.as_str();
294                }
295                _ => {
296                    // the second argument is not processed, just forwarded
297                    action += ", ";
298                    action += inner_pair.as_str();
299                }
300            }
301        }
302        action += ")";
303
304        Ok(action)
305    }
306
307    fn keymap_parser(
308        layer_keys: &str,
309        aliases: &HashMap<String, String>,
310        layer_names: &HashMap<String, u32>,
311    ) -> Result<Vec<String>, String> {
312        //resolve aliases first
313        let layer_keys = Self::alias_resolver(layer_keys, aliases)?;
314
315        let mut key_action_sequence = Vec::new();
316
317        // Parse the keymap using Pest
318        match ConfigParser::parse(Rule::key_map, &layer_keys) {
319            Ok(pairs) => {
320                // The top-level pair is 'key_map'. We need to iterate its inner content.
321                for pair in pairs {
322                    // Should only be one pair matching Rule::key_map
323                    if pair.as_rule() == Rule::key_map {
324                        for inner_pair in pair.into_inner() {
325                            match inner_pair.as_rule() {
326                                Rule::no_action => {
327                                    let action = inner_pair.as_str().to_string();
328                                    key_action_sequence.push(action);
329                                }
330
331                                Rule::transparent_action => {
332                                    let action = inner_pair.as_str().to_string();
333                                    key_action_sequence.push(action);
334                                }
335
336                                Rule::simple_keycode => {
337                                    let action = inner_pair.as_str().to_string();
338                                    key_action_sequence.push(action);
339                                }
340
341                                Rule::shifted_action => {
342                                    let action = inner_pair.as_str().to_string();
343                                    key_action_sequence.push(action);
344                                }
345
346                                Rule::osm_action => {
347                                    let action = inner_pair.as_str().to_string();
348                                    key_action_sequence.push(action);
349                                }
350
351                                Rule::wm_action => {
352                                    let action = inner_pair.as_str().to_string();
353                                    key_action_sequence.push(action);
354                                }
355
356                                //layer actions:
357                                Rule::df_action => {
358                                    key_action_sequence.push(Self::layer_name_resolver("DF", inner_pair, layer_names)?);
359                                }
360                                Rule::mo_action => {
361                                    key_action_sequence.push(Self::layer_name_resolver("MO", inner_pair, layer_names)?);
362                                }
363                                Rule::lm_action => {
364                                    key_action_sequence.push(Self::layer_name_resolver("LM", inner_pair, layer_names)?);
365                                }
366                                Rule::lt_action => {
367                                    key_action_sequence.push(Self::layer_name_resolver("LT", inner_pair, layer_names)?);
368                                    //"LT(".to_owned() + &Self::layer_name_resolver(inner_pair, layer_names)? + ")");
369                                }
370                                Rule::osl_action => {
371                                    key_action_sequence.push(Self::layer_name_resolver(
372                                        "OSL",
373                                        inner_pair,
374                                        layer_names,
375                                    )?);
376                                }
377                                Rule::tt_action => {
378                                    key_action_sequence.push(Self::layer_name_resolver("TT", inner_pair, layer_names)?);
379                                }
380                                Rule::tg_action => {
381                                    key_action_sequence.push(Self::layer_name_resolver("TG", inner_pair, layer_names)?);
382                                }
383                                Rule::to_action => {
384                                    key_action_sequence.push(Self::layer_name_resolver("TO", inner_pair, layer_names)?);
385                                }
386
387                                //tap-hold actions:
388                                Rule::mt_action => {
389                                    let action = inner_pair.as_str().to_string();
390                                    key_action_sequence.push(action);
391                                }
392                                Rule::th_action => {
393                                    let action = inner_pair.as_str().to_string();
394                                    key_action_sequence.push(action);
395                                }
396
397                                Rule::EOI | Rule::WHITESPACE => {
398                                    // Ignore End of input marker
399                                }
400                                _ => {
401                                    // This case should not be reached
402                                    return Err(format!(
403                                        "Unexpected rule encountered during layer.keys processing:{:?}",
404                                        inner_pair.as_rule()
405                                    ));
406                                }
407                            }
408                        }
409                    }
410                }
411            }
412            Err(e) => {
413                return Err(format!("Invalid keymap format: {}", e));
414            }
415        }
416
417        Ok(key_action_sequence)
418    }
419}