kanata_keyberon_macros/
lib.rs

1extern crate proc_macro;
2use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree};
3use quote::quote;
4
5#[proc_macro]
6pub fn layout(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
7    let input: TokenStream = input.into();
8
9    let mut out = TokenStream::new();
10
11    let mut inside = TokenStream::new();
12
13    for t in input {
14        match t {
15            TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => {
16                let layer = parse_layer(g.stream());
17                inside.extend(quote! {
18                    [#layer],
19                });
20            }
21            _ => panic!("{}", "Invalid token, expected layer: {{ ... }}"),
22        }
23    }
24
25    let all: TokenStream = quote! { [#inside] };
26    out.extend(all);
27
28    out.into()
29}
30
31fn parse_layer(input: TokenStream) -> TokenStream {
32    let mut out = TokenStream::new();
33    for t in input {
34        match t {
35            TokenTree::Group(g) if g.delimiter() == Delimiter::Bracket => {
36                let row = parse_row(g.stream());
37                out.extend(quote! {
38                    [#row],
39                });
40            }
41            TokenTree::Punct(p) if p.as_char() == ',' => (),
42            _ => panic!("Invalid token, expected row: [ ... ]"),
43        }
44    }
45    out
46}
47
48fn parse_row(input: TokenStream) -> TokenStream {
49    let mut out = TokenStream::new();
50    for t in input {
51        match t {
52            TokenTree::Ident(i) => match i.to_string().as_str() {
53                "n" => out.extend(quote! { keyberon::action::Action::NoOp, }),
54                "t" => out.extend(quote! { keyberon::action::Action::Trans, }),
55                _ => out.extend(quote! {
56                    keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::#i),
57                }),
58            },
59            TokenTree::Punct(p) => punctuation_to_keycode(&p, &mut out),
60            TokenTree::Literal(l) => literal_to_keycode(&l, &mut out),
61            TokenTree::Group(g) => parse_group(&g, &mut out),
62        }
63    }
64    out
65}
66
67fn parse_group(g: &Group, out: &mut TokenStream) {
68    match g.delimiter() {
69        // Handle empty groups
70        Delimiter::Parenthesis if g.stream().is_empty() => {
71            eprintln!("Expected a layer number in layer switch");
72        }
73        Delimiter::Brace if g.stream().is_empty() => {
74            eprintln!("Expected an action - group cannot be empty");
75        }
76        Delimiter::Bracket if g.stream().is_empty() => {
77            eprintln!("Expected keycodes - keycode group cannot be empty");
78        }
79
80        // Momentary layer switch (Action::Layer)
81        Delimiter::Parenthesis => {
82            let tokens = g.stream();
83            out.extend(quote! { keyberon::action::Action::Layer(#tokens), });
84        }
85        // Pass the expression unchanged (adding a comma after it)
86        Delimiter::Brace => out.extend(g.stream().into_iter().chain(TokenStream::from(
87            TokenTree::Punct(Punct::new(',', Spacing::Alone)),
88        ))),
89        // Multiple keycodes (Action::MultipleKeyCodes)
90        Delimiter::Bracket => parse_keycode_group(g.stream(), out),
91
92        // Is this reachable?
93        Delimiter::None => eprintln!("Unexpected group"),
94    }
95}
96
97fn parse_keycode_group(input: TokenStream, out: &mut TokenStream) {
98    let mut inner = TokenStream::new();
99    for t in input {
100        match t {
101            TokenTree::Ident(i) => inner.extend(quote! {
102                keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::#i),
103            }),
104            TokenTree::Punct(p) => punctuation_to_keycode(&p, &mut inner),
105            TokenTree::Literal(l) => literal_to_keycode(&l, &mut inner),
106            TokenTree::Group(g) => parse_group(&g, &mut inner),
107        }
108    }
109    out.extend(quote! { keyberon::action::Action::MultipleActions(&[#inner].as_slice()), });
110}
111
112fn punctuation_to_keycode(p: &Punct, out: &mut TokenStream) {
113    match p.as_char() {
114        // Normal punctuation
115        '-' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Minus), }),
116        '=' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Equal), }),
117        ';' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::SColon), }),
118        ',' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Comma), }),
119        '.' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Dot), }),
120        '/' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Slash), }),
121
122        // Shifted punctuation
123        '!' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb1].as_slice()), }),
124        '@' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb2].as_slice()), }),
125        '#' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb3].as_slice()), }),
126        '$' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb4].as_slice()), }),
127        '%' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb5].as_slice()), }),
128        '^' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb6].as_slice()), }),
129        '&' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb7].as_slice()), }),
130        '*' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb8].as_slice()), }),
131        '_' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Minus].as_slice()), }),
132        '+' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Equal].as_slice()), }),
133        '|' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Bslash].as_slice()), }),
134        '~' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Grave].as_slice()), }),
135        '<' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Comma].as_slice()), }),
136        '>' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Dot].as_slice()), }),
137        '?' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Slash].as_slice()), }),
138        // Is this reachable?
139        _ => eprintln!("Punctuation could not be parsed as a keycode")
140    }
141}
142
143fn literal_to_keycode(l: &Literal, out: &mut TokenStream) {
144    //let repr = l.to_string();
145    match l.to_string().as_str() {
146        "1" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb1), }),
147        "2" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb2), }),
148        "3" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb3), }),
149        "4" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb4), }),
150        "5" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb5), }),
151        "6" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb6), }),
152        "7" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb7), }),
153        "8" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb8), }),
154        "9" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb9), }),
155        "0" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb0), }),
156
157        // Char literals; mostly punctuation which can't be properly tokenized alone
158        r#"'\''"# => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Quote), }),
159        r#"'\\'"# => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Bslash), }),
160        // Shifted characters
161        "'['" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::LBracket), }),
162        "']'" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::RBracket), }),
163        "'`'" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Grave), }),
164        "'\"'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Quote].as_slice()), }),
165        "'('" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb9].as_slice()), }),
166        "')'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb0].as_slice()), }),
167        "'{'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::LBracket].as_slice()), }),
168        "'}'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::RBracket].as_slice()), }),
169        "'_'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Minus].as_slice()), }),
170
171        s if s.starts_with('\'') => eprintln!("Literal could not be parsed as a keycode"),
172
173        s if s.starts_with('\"')  => {
174            eprintln!("Typing strings on key press is not yet supported")
175        }
176        _ => eprintln!("Literal could not be parsed as a keycode")
177    }
178}