kll_macros/
lib.rs

1// Copyright 2021 Jacob Alexander
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.
7
8use proc_macro::{TokenStream, TokenTree};
9use std::iter::FromIterator;
10
11/// Takes a list of sequences of combos and turns it into a u8 array
12/// that can be stored in memory as a contiguous piece of data.
13/// This is necessary to store the trigger guide independently of rust compilation.
14///
15/// ```
16/// use kll_core::{Capability, CapabilityState, TriggerCondition, trigger};
17///
18/// const TRIGGER_GUIDES: &'static [u8] = kll_macros::trigger_guide!(
19///     [[
20///         TriggerCondition::Switch {
21///             state: trigger::Phro::Hold,
22///             index: 6,
23///             loop_condition_index: 0,
24///         },
25///         TriggerCondition::Switch {
26///             state: trigger::Phro::Hold,
27///             index: 7,
28///             loop_condition_index: 0,
29///         },
30///     ]],
31///     [[
32///         TriggerCondition::Switch {
33///             state: trigger::Phro::Hold,
34///             index: 6,
35///             loop_condition_index: 0,
36///         },
37///     ]],
38///     [[
39///         TriggerCondition::Layer {
40///             state: trigger::LayerState::ShiftActivate,
41///             layer: 3,
42///             loop_condition_index: 0,
43///         },
44///     ]],
45///     [[
46///         TriggerCondition::AnalogDistance {
47///             reserved: 0,
48///             index: 8,
49///             val: 1500,
50///         },
51///     ]],
52/// );
53/// ```
54#[proc_macro]
55pub fn trigger_guide(input: TokenStream) -> TokenStream {
56    let mut output: Vec<String> = vec!["unsafe { &[".to_string()];
57
58    for sequence in input {
59        match sequence {
60            TokenTree::Group(sequence) => {
61                for combo in sequence.stream() {
62                    match combo {
63                        TokenTree::Group(combo) => {
64                            let mut combo_output: Vec<String> = Vec::new();
65                            let mut prefix_output: Vec<String> = Vec::new();
66                            let mut elem_count = 0;
67                            // Largest enum is 6 bytes
68                            // Adjusts the size depending on the enum
69                            let mut byte_count = 0;
70                            let byte_max_count = 6;
71                            for elem in combo.stream() {
72                                // Detect TriggerCondition
73                                match elem.clone() {
74                                    TokenTree::Ident(ident) => {
75                                        // Check for hardcoded byte count
76                                        // NOTE: It may be possible to use std::mem::size_of
77                                        //       if we make a new struct crate that this macro
78                                        //       crate can depend on. Determining byte count is the
79                                        //       most important feature of this macro. This is
80                                        //       needed to prevent undefined struct memory accesses
81                                        //       when building the const u8 array.
82                                        match ident.to_string().as_str() {
83                                            "TriggerCondition" => {
84                                                // New element
85                                                elem_count += 1;
86                                                prefix_output = Vec::new();
87                                            }
88                                            "Switch" | "Animation" => {
89                                                byte_count = 6;
90                                            }
91                                            "AnalogDistance" | "AnalogVelocity"
92                                            | "AnalogAcceleration" | "AnalogJerk" => {
93                                                byte_count = 6;
94                                            }
95                                            "HidLed" | "Layer" | "Rotation" => {
96                                                byte_count = 5;
97                                            }
98                                            "Sleep" | "Resume" | "Inactive" | "Active" => {
99                                                byte_count = 4;
100                                            }
101                                            _ => {
102                                                panic!("Unknown elem ident: {:?}", ident);
103                                            }
104                                        }
105                                    }
106                                    TokenTree::Punct(_) => {}
107                                    TokenTree::Group(group) => {
108                                        // Finished element, prepare additions
109                                        for n in 0..byte_count {
110                                            combo_output.append(&mut prefix_output.clone());
111                                            combo_output.push(group.to_string());
112                                            combo_output.push(".bytes()[".to_string());
113                                            combo_output.push(n.to_string());
114                                            combo_output.push("],".to_string());
115                                        }
116                                        // Fill empty bytes (to prevent undefined struct access)
117                                        for _ in byte_count..byte_max_count {
118                                            combo_output.push("0,".to_string());
119                                        }
120                                    }
121                                    _ => {}
122                                }
123                                prefix_output.push(elem.to_string());
124                            }
125
126                            // Add combo element count
127                            output.push(elem_count.to_string());
128                            output.push(",".to_string());
129                            // Add combo
130                            output.append(&mut combo_output);
131                        }
132                        TokenTree::Punct(_) => {}
133                        _ => {
134                            panic!("Invalid combo element: {:?}", combo);
135                        }
136                    }
137                }
138            }
139            TokenTree::Punct(_) => {}
140            _ => {
141                panic!("Invalid sequence element: {:?}", sequence);
142            }
143        }
144    }
145
146    // Final 0 length sequence to indicate finished
147    output.push("0 ] }".to_string());
148    String::from_iter(output).parse().unwrap()
149}
150
151/// Takes a list of sequences of combos of Capabilities and turns it into a u8 array
152/// that can be stored in memory as a contiguous piece of data.
153/// This is necessary to store the result guide independently of rust compilation.
154///
155/// ```
156/// use kll_core::{Capability, CapabilityState};
157///
158/// const RESULT_GUIDES: &'static [u8] = kll_macros::result_guide!(
159///     [
160///         // Press Shift + A; Release Shift; Release A
161///         [
162///             Capability::HidKeyboard {
163///                 state: CapabilityState::Initial,
164///                 loop_condition_index: 0,
165///                 id: kll_hid::Keyboard::A,
166///             },
167///             Capability::HidKeyboard {
168///                 state: CapabilityState::Initial,
169///                 loop_condition_index: 0,
170///                 id: kll_hid::Keyboard::LeftShift,
171///             },
172///         ],
173///         [Capability::HidKeyboard {
174///             state: CapabilityState::Last,
175///             loop_condition_index: 0,
176///             id: kll_hid::Keyboard::LeftShift,
177///         },],
178///         [Capability::HidKeyboard {
179///             state: CapabilityState::Last,
180///             loop_condition_index: 0,
181///             id: kll_hid::Keyboard::A,
182///         },],
183///     ],
184///     // Press B
185///     [[Capability::HidKeyboard {
186///         state: CapabilityState::Initial,
187///         loop_condition_index: 0,
188///         id: kll_hid::Keyboard::B,
189///     },]],
190///     // Release B
191///     [[Capability::HidKeyboard {
192///         state: CapabilityState::Last,
193///         loop_condition_index: 0,
194///         id: kll_hid::Keyboard::B,
195///     },]],
196/// );
197/// ```
198#[proc_macro]
199pub fn result_guide(input: TokenStream) -> TokenStream {
200    let mut output: Vec<String> = vec!["unsafe { &[".to_string()];
201
202    for sequence in input {
203        match sequence {
204            TokenTree::Group(sequence) => {
205                for combo in sequence.stream() {
206                    match combo {
207                        TokenTree::Group(combo) => {
208                            let mut combo_output: Vec<String> = Vec::new();
209                            let mut prefix_output: Vec<String> = Vec::new();
210                            let mut elem_count = 0;
211                            // Largest enum is 8 bytes
212                            // Adjusts the size depending on the enum
213                            let mut byte_count = 0;
214                            let byte_max_count = 8;
215                            for elem in combo.stream() {
216                                // Detect TriggerCondition
217                                match elem.clone() {
218                                    TokenTree::Ident(ident) => {
219                                        // Check for hardcoded byte count
220                                        // NOTE: It may be possible to use std::mem::size_of
221                                        //       if we make a new struct crate that this macro
222                                        //       crate can depend on. Determining byte count is the
223                                        //       most important feature of this macro. This is
224                                        //       needed to prevent undefined struct memory accesses
225                                        //       when building the const u8 array.
226                                        match ident.to_string().as_str() {
227                                            "Capability" => {
228                                                // New element
229                                                elem_count += 1;
230                                                prefix_output = Vec::new();
231                                            }
232                                            "LayerClear" | "McuFlashMode" | "NoOp" => {
233                                                byte_count = 4;
234                                            }
235                                            "HidKeyboard"
236                                            | "HidProtocol"
237                                            | "HidLed"
238                                            | "HidSystemControl"
239                                            | "LayerRotate"
240                                            | "PixelAnimationControl"
241                                            | "PixelFadeLayer"
242                                            | "PixelGammaControl" => {
243                                                byte_count = 5;
244                                            }
245                                            "HidioOpenUrl"
246                                            | "HidioUnicodeString"
247                                            | "HidConsumerControl"
248                                            | "HidKeyboardState"
249                                            | "LayerState"
250                                            | "PixelAnimationIndex"
251                                            | "PixelLedControl"
252                                            | "Rotate" => {
253                                                byte_count = 6;
254                                            }
255                                            "PixelFadeIndex" | "PixelFadeSet" | "PixelTest" => {
256                                                byte_count = 7;
257                                            }
258                                            "HidioUnicodeState" => {
259                                                byte_count = 8;
260                                            }
261                                            _ => {
262                                                panic!("Unknown elem ident: {:?}", ident);
263                                            }
264                                        }
265                                    }
266                                    TokenTree::Punct(_) => {}
267                                    TokenTree::Group(group) => {
268                                        // Finished element, prepare additions
269                                        for n in 0..byte_count {
270                                            combo_output.append(&mut prefix_output.clone());
271                                            combo_output.push(group.to_string());
272                                            combo_output.push(".bytes()[".to_string());
273                                            combo_output.push(n.to_string());
274                                            combo_output.push("],".to_string());
275                                        }
276                                        // Fill empty bytes (to prevent undefined struct access)
277                                        for _ in byte_count..byte_max_count {
278                                            combo_output.push("0,".to_string());
279                                        }
280                                    }
281                                    _ => {}
282                                }
283                                prefix_output.push(elem.to_string());
284                            }
285
286                            // Add combo element count
287                            output.push(elem_count.to_string());
288                            output.push(",".to_string());
289                            // Add combo
290                            output.append(&mut combo_output);
291                        }
292                        TokenTree::Punct(_) => {}
293                        _ => {
294                            panic!("Invalid combo element: {:?}", combo);
295                        }
296                    }
297                }
298            }
299            TokenTree::Punct(_) => {}
300            _ => {
301                panic!("Invalid sequence element: {:?}", sequence);
302            }
303        }
304    }
305
306    // Final 0 length sequence to indicate finished
307    output.push("0 ] }".to_string());
308    String::from_iter(output).parse().unwrap()
309}
310
311enum LayerLookupState {
312    Layer,
313    LayerComma,
314    Type,
315    TypeComma,
316    Index,
317    IndexComma,
318    Triggers,
319    TriggersComma,
320}
321
322/// Takes data in the following format and turns it into a byte array.
323/// The trigger count is automatically calculated.
324/// Triggers are u16, all the other fields are u8.
325///
326/// ```
327/// const LAYER_LOOKUP: &'static [u8] = kll_macros::layer_lookup!(
328///     // Layer 0, Switch Type (1), Index 5, No Triggers
329///     0, 1, 5, [],
330///     // Layer 0, Switch Type (1), Index 6, 2 Triggers: 0 14
331///     0, 1, 6, [0, 14],
332///     // Layer 0, Switch Type (1), Index 7, 1 Trigger: 0
333///     0, 1, 7, [0],
334///     // Layer 1, None Type (0), Index 2, No Triggers
335///     1, 0, 2, [],
336///     // Layer 1, Layer Type (7), Layer(index) 3, 1 Trigger: A
337///     1, 7, 3, [0xA],
338///     // Layer 2, AnalogDistance Type (3), Index 8, 1 Trigger: A
339///     2, 3, 8, [0xA],
340///     // Layer 2, Switch Type (1), Index 6, 1 Trigger: 14
341///     2, 1, 6, [14],
342/// );
343/// ```
344///
345#[proc_macro]
346pub fn layer_lookup(input: TokenStream) -> TokenStream {
347    let mut state = LayerLookupState::Layer;
348
349    let mut triggers: Vec<u16> = Vec::new();
350
351    let mut output: Vec<String> = vec!["&".to_string(), "[".to_string()];
352
353    // TODO Add error checking for syntax
354    for token in input {
355        match state {
356            LayerLookupState::Layer => {
357                output.push(token.to_string());
358                state = LayerLookupState::LayerComma;
359            }
360            LayerLookupState::LayerComma => {
361                output.push(token.to_string());
362                state = LayerLookupState::Type;
363            }
364            LayerLookupState::Type => {
365                output.push(token.to_string());
366                state = LayerLookupState::TypeComma;
367            }
368            LayerLookupState::TypeComma => {
369                output.push(token.to_string());
370                state = LayerLookupState::Index;
371            }
372            LayerLookupState::Index => {
373                match token {
374                    TokenTree::Literal(literal) => {
375                        // Check if this is a hex value
376                        let val = if literal.to_string().contains("0x") {
377                            u16::from_str_radix(literal.to_string().trim_start_matches("0x"), 16)
378                                .unwrap()
379                        } else {
380                            literal.to_string().parse::<u16>().unwrap()
381                        };
382
383                        // Push index as two bytes
384                        for num in val.to_le_bytes() {
385                            output.push(format!("{}, ", num));
386                        }
387                    }
388                    _ => {
389                        panic!("Invalid token, expected index token: {:?}", token);
390                    }
391                }
392                state = LayerLookupState::IndexComma;
393            }
394            LayerLookupState::IndexComma => {
395                // Comma has already been added due to the index
396                state = LayerLookupState::Triggers;
397            }
398            LayerLookupState::Triggers => {
399                match token {
400                    TokenTree::Group(group) => {
401                        for subtoken in group.stream() {
402                            match subtoken.clone() {
403                                TokenTree::Punct(_) => {}
404                                TokenTree::Literal(literal) => {
405                                    // Check if this is a hex value
406                                    if literal.to_string().contains("0x") {
407                                        triggers.push(
408                                            u16::from_str_radix(
409                                                literal.to_string().trim_start_matches("0x"),
410                                                16,
411                                            )
412                                            .unwrap(),
413                                        );
414                                    } else {
415                                        triggers.push(literal.to_string().parse::<u16>().unwrap());
416                                    }
417                                }
418                                _ => {
419                                    panic!("Invalid trigger list token: {:?}", subtoken);
420                                }
421                            }
422                        }
423
424                        // Finished gathering triggers
425                        // 1. Add the count
426                        output.push(format!("{},", triggers.len()));
427
428                        // 2. Add each of the triggers as a little endian u16
429                        if !triggers.is_empty() {
430                            for trigger in triggers {
431                                for num in trigger.to_le_bytes() {
432                                    output.push(format!("{},", num));
433                                }
434                            }
435                            triggers = Vec::new();
436                        }
437                        state = LayerLookupState::TriggersComma;
438                    }
439                    _ => {
440                        panic!("Invalid trigger token group: {:?}", token);
441                    }
442                }
443            }
444            LayerLookupState::TriggersComma => {
445                state = LayerLookupState::Layer;
446            }
447        }
448    }
449
450    output.push("]".to_string());
451    String::from_iter(output).parse().unwrap()
452}