From e50c4559ca539d310cec158c5ae122415411b852 Mon Sep 17 00:00:00 2001 From: Skelebot Date: Wed, 21 Apr 2021 16:11:19 +0200 Subject: [PATCH 1/4] Add a procedural macro for defining layouts --- .gitignore | 4 +- Cargo.toml | 1 + layout-macro/Cargo.toml | 16 ++++ layout-macro/src/lib.rs | 190 ++++++++++++++++++++++++++++++++++++++ layout-macro/tests/mod.rs | 81 ++++++++++++++++ src/layout.rs | 45 +++++++++ 6 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 layout-macro/Cargo.toml create mode 100644 layout-macro/src/lib.rs create mode 100644 layout-macro/tests/mod.rs diff --git a/.gitignore b/.gitignore index afe70d9..18c3e08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/Cargo.lock -/target +Cargo.lock +target/ **/*.rs.bk *~ diff --git a/Cargo.toml b/Cargo.toml index d3552c6..e39d865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT" readme = "README.md" [dependencies] +layout-macro = { version = "0.1.0", path = "./layout-macro" } either = { version = "1.6", default-features = false } generic-array = "0.14" embedded-hal = { version = "0.2", features = ["unproven"] } diff --git a/layout-macro/Cargo.toml b/layout-macro/Cargo.toml new file mode 100644 index 0000000..d33c377 --- /dev/null +++ b/layout-macro/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "layout-macro" +version = "0.1.0" +authors = ["Antoni Simka "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +proc-macro-error = "1.0.4" +proc-macro2 = "1.0" +quote = "1.0" + +[dev-dependencies] +keyberon = { path = "../" } \ No newline at end of file diff --git a/layout-macro/src/lib.rs b/layout-macro/src/lib.rs new file mode 100644 index 0000000..5dfb370 --- /dev/null +++ b/layout-macro/src/lib.rs @@ -0,0 +1,190 @@ +extern crate proc_macro; +use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree}; +use proc_macro_error::proc_macro_error; +use proc_macro_error::{abort, emit_error}; +use quote::quote; + +#[proc_macro_error] +#[proc_macro] +pub fn layout(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input: TokenStream = input.into(); + + let mut out = TokenStream::new(); + + let mut inside = TokenStream::new(); + + for t in input { + match t { + TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => { + let layer = parse_layer(g.stream()); + inside.extend(quote! { + &[#layer], + }); + } + _ => abort!(t, "Invalid token, expected layer: {{ ... }}"), + } + } + + let all: TokenStream = quote! { &[#inside] }; + out.extend(all); + + out.into() +} + +fn parse_layer(input: TokenStream) -> TokenStream { + let mut out = TokenStream::new(); + for t in input { + match t { + TokenTree::Group(g) if g.delimiter() == Delimiter::Bracket => { + let row = parse_row(g.stream()); + out.extend(quote! { + &[#row], + }); + } + TokenTree::Punct(p) if p.as_char() == ',' => (), + _ => abort!(t, "Invalid token, expected row: [ ... ]"), + } + } + out +} + +fn parse_row(input: TokenStream) -> TokenStream { + let mut out = TokenStream::new(); + for t in input { + match t { + TokenTree::Ident(i) => match i.to_string().as_str() { + "n" => out.extend(quote! { keyberon::action::Action::NoOp, }), + "t" => out.extend(quote! { keyberon::action::Action::Trans, }), + _ => out.extend(quote! { + keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::#i), + }), + }, + TokenTree::Punct(p) => punctuation_to_keycode(&p, &mut out), + TokenTree::Literal(l) => literal_to_keycode(&l, &mut out), + TokenTree::Group(g) => parse_group(&g, &mut out), + } + } + out +} + +fn parse_group(g: &Group, out: &mut TokenStream) { + match g.delimiter() { + // Handle empty groups + Delimiter::Parenthesis if g.stream().is_empty() => { + emit_error!(g, "Expected a layer number in layer switch"; help = "To create a parenthesis keycode, enclose it in apostrophes: '('") + } + Delimiter::Brace if g.stream().is_empty() => { + emit_error!(g, "Expected an action - group cannot be empty"; help = "To create a brace keycode, enclose it in apostrophes: '{'") + } + Delimiter::Bracket if g.stream().is_empty() => { + emit_error!(g, "Expected keycodes - keycode group cannot be empty"; help = "To create a bracket keycode, enclose it in apostrophes: '['") + } + + // Momentary layer switch (Action::Layer) + Delimiter::Parenthesis => { + let tokens = g.stream(); + out.extend(quote! { keyberon::action::Action::Layer(#tokens), }); + } + // Pass the expression unchanged (adding a comma after it) + Delimiter::Brace => out.extend(g.stream().into_iter().chain(TokenStream::from( + TokenTree::Punct(Punct::new(',', Spacing::Alone)), + ))), + // Multiple keycodes (Action::MultipleKeyCodes) + Delimiter::Bracket => parse_keycode_group(g.stream(), out), + + // Is this reachable? + Delimiter::None => emit_error!(g, "Unexpected group"), + } +} + +fn parse_keycode_group(input: TokenStream, out: &mut TokenStream) { + let mut inner = TokenStream::new(); + for t in input { + match t { + TokenTree::Ident(i) => inner.extend(quote! { + keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::#i), + }), + TokenTree::Punct(p) => punctuation_to_keycode(&p, &mut inner), + TokenTree::Literal(l) => literal_to_keycode(&l, &mut inner), + TokenTree::Group(g) => parse_group(&g, &mut inner), + } + } + out.extend(quote! { keyberon::action::Action::MultipleActions(&[#inner]) }); +} + +fn punctuation_to_keycode(p: &Punct, out: &mut TokenStream) { + match p.as_char() { + // Normal punctuation + '-' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Minus), }), + '=' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Equal), }), + ';' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::SColon), }), + ',' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Comma), }), + '.' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Dot), }), + '/' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Slash), }), + + // Shifted punctuation + '!' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb1]), }), + '@' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb2]), }), + '#' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb3]), }), + '$' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb4]), }), + '%' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb5]), }), + '^' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb6]), }), + '&' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb7]), }), + '*' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb8]), }), + '_' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Minus]), }), + '+' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Equal]), }), + '|' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Bslash]), }), + '~' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Grave]), }), + '<' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Comma]), }), + '>' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Dot]), }), + '?' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Slash]), }), + // Is this reachable? + _ => emit_error!(p, "Punctuation could not be parsed as a keycode") + } +} + +fn literal_to_keycode(l: &Literal, out: &mut TokenStream) { + let repr = l.to_string(); + match repr.chars().next().unwrap() { + '0'..='9' if repr.len() == 1 => { + match repr.chars().next().unwrap() { + '1' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb1), }), + '2' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb2), }), + '3' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb3), }), + '4' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb4), }), + '5' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb5), }), + '6' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb6), }), + '7' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb7), }), + '8' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb8), }), + '9' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb9), }), + '0' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb0), }), + _ => unreachable!() + } + } + // Char literals; mostly punctuation which can't be properly tokenized alone + '\'' => { + match repr.chars().nth(1).unwrap() { + '\\' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Bslash), }), + '[' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::LBracket), }), + ']' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::RBracket), }), + + '`' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Grave), }), + '\'' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Quote), }), + '"' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Quote]), }), + '(' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb9]), }), + ')' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb0]), }), + '{' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::LBracket]), }), + '}' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::RBracket]), }), + '_' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Minus]), }), + _ => emit_error!(l, "Literal could not be parsed as a keycode"; help = "Maybe try without quotes?") + } + } + '"' => if repr.len() == 3 { + emit_error!(l, "Typing strings on key press is not yet supported"; help = "Did you mean to use apostrophes instead of quotes?"); + } else { + emit_error!(l, "Typing strings on key press is not yet supported"); + } + // Is this reachable? + _ => emit_error!(l, "Literal could not be parsed as a keycode") + } +} diff --git a/layout-macro/tests/mod.rs b/layout-macro/tests/mod.rs new file mode 100644 index 0000000..d33d293 --- /dev/null +++ b/layout-macro/tests/mod.rs @@ -0,0 +1,81 @@ +extern crate layout_macro; +use layout_macro::layout; +use keyberon::action::{Action, k, l, m, HoldTapConfig, Action::*}; +use keyberon::key_code::KeyCode::*; + +#[test] +fn test_layout_equality() { + + macro_rules! s { + ($k:expr) => { + m(&[LShift, $k]) + }; + } + + static S_ENTER: Action = Action::HoldTap { + timeout: 280, + hold: &Action::KeyCode(RShift), + tap: &Action::KeyCode(Enter), + config: HoldTapConfig::PermissiveHold, + tap_hold_interval: 0, + }; + + #[rustfmt::skip] + pub static LAYERS_OLD: keyberon::layout::Layers = &[ + &[ + &[k(Tab), k(Q), k(W), k(E), k(R), k(T), k(Y), k(U), k(I), k(O), k(P), k(BSpace)], + &[k(LCtrl), k(A), k(S), k(D), k(F), k(G), k(H), k(J), k(K), k(L), k(SColon), k(Quote) ], + &[k(LShift), k(Z), k(X), k(C), k(V), k(B), k(N), k(M), k(Comma), k(Dot), k(Slash), k(Escape)], + &[NoOp, NoOp, k(LGui), l(1), k(Space), k(Escape), k(BSpace), S_ENTER, l(1), k(RAlt), NoOp, NoOp], + ], + &[ + &[k(Tab), k(Kb1), k(Kb2), k(Kb3), k(Kb4), k(Kb5), k(Kb6), k(Kb7), k(Kb8), k(Kb9), k(Kb0), k(BSpace)], + &[k(LCtrl), s!(Kb1), s!(Kb2), s!(Kb3), s!(Kb4), s!(Kb5), s!(Kb6), s!(Kb7), s!(Kb8), s!(Kb9), s!(Kb0), MultipleActions(&[k(LCtrl), k(Grave)])], + &[k(LShift), NoOp, NoOp, NoOp, NoOp, NoOp, k(Left), k(Down), k(Up), k(Right), NoOp, s!(Grave)], + &[NoOp, NoOp, k(LGui), Trans, Trans, Trans, Trans, Trans, Trans, k(RAlt), NoOp, NoOp], + ], + ]; + + pub static LAYERS: keyberon::layout::Layers = layout! { + { + [ Tab Q W E R T Y U I O P BSpace ] + [ LCtrl A S D F G H J K L ; Quote ] + [ LShift Z X C V B N M , . / Escape ] + [ n n LGui (1) Space Escape BSpace {S_ENTER} (1) RAlt n n ] + } + { + [ Tab 1 2 3 4 5 6 7 8 9 0 BSpace ] + [ LCtrl ! @ # $ % ^ & * '(' ')' [LCtrl '`'] ] + [ LShift n n n n n Left Down Up Right n ~ ] + [ n n LGui t t t t t t RAlt n n ] + } + }; + + assert_eq!(LAYERS, LAYERS_OLD); + use std::mem::size_of_val; + assert_eq!(size_of_val(LAYERS), size_of_val(LAYERS_OLD)) +} + +#[test] +fn test_nesting() { + static A: keyberon::layout::Layers = layout! { + { + [{k(D)} [(5) [C {k(D)}]]] + } + }; + static B: keyberon::layout::Layers = &[ + &[ + &[k(D), Action::MultipleActions(&[Action::Layer(5), Action::MultipleActions(&[k(C), k(D)])])] + ] + ]; + assert_eq!(A, B); +} + +#[test] +fn test_layer_switch() { + static A: keyberon::layout::Layers = layout! { + { + [(0xa), (0b0110), (b'a' as usize), (1 + 8 & 32), ([4,5][0])] + } + }; +} diff --git a/src/layout.rs b/src/layout.rs index eb4e2c2..2556d7b 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,5 +1,50 @@ //! Layout management. +/// A procedural macro to generate [Layers](type.Layers.html) +/// ## Syntax +/// Items inside the macro are converted to Actions as such: +/// - [`Action::KeyCode`]: Idents are automatically understood as keycodes: `A`, `RCtrl`, `Space` +/// - Punctuation, numbers and other literals that aren't special to the rust parser are converted +/// to KeyCodes as well: `,` becomes `KeyCode::Commma`, `2` becomes `KeyCode::Kb2`, `/` becomes `KeyCode::Slash` +/// - Characters which require shifted keys are converted to `Action::MultipleKeyCodes(&[LShift, ])`: +/// `!` becomes `Action::MultipleKeyCodes(&[LShift, Kb1])` etc +/// - Characters special to the rust parser (parentheses, brackets, braces, quotes, apostrophes, underscores, backslashes and backticks) +/// left alone cause parsing errors and as such have to be enclosed by apostrophes: `'['` becomes `KeyCode::LBracket`, +/// `'\''` becomes `KeyCode::Quote`, `'\\'` becomes `KeyCode::BSlash` +/// - [`Action::NoOp`]: Lowercase `n` +/// - [`Action::Trans`]: Lowercase `t` +/// - [`Action::Layer`]: A number in parentheses: `(1)`, `(4 - 2)`, `(0x4u8 as usize)` +/// - [`Action::MultipleActions`]: Actions in brackets: `[LCtrl S]`, `[LAlt LCtrl C]`, `[(2) B {Action::NoOp}]` +/// - Other `Action`s: anything in braces (`{}`) is copied unchanged to the final layout - `{ Action::Custom(42) }` +/// simply becomes `Action::Custom(42)` +/// +/// **Important note**: comma (`,`) is a keycode on its own, and can't be used to separate keycodes as one would have +/// to do when not using a macro. +/// +/// ## Usage example: +/// Example layout for a 4x12 split keyboard: +/// ``` +/// use keyberon::action::Action; +/// static DLAYER: Action = Action::DefaultLayer(5); +/// +/// pub static LAYERS: keyberon::layout::Layers = keyberon::layout::layout! { +/// { +/// [ Tab Q W E R T Y U I O P BSpace ] +/// [ LCtrl A S D F G H J K L ; Quote ] +/// [ LShift Z X C V B N M , . / Escape ] +/// [ n n LGui {DLAYER} Space Escape BSpace Enter (1) RAlt n n ] +/// } +/// { +/// [ Tab 1 2 3 4 5 6 7 8 9 0 BSpace ] +/// [ LCtrl ! @ # $ % ^ & * '(' ')' - = ] +/// [ LShift n n n n n n n n n n [LAlt A]] +/// [ n n LGui (2) t t t t t RAlt n n ] +/// } +/// // ... +/// }; +/// ``` +pub use layout_macro::layout; + use crate::action::{Action, HoldTapConfig}; use crate::key_code::KeyCode; use arraydeque::ArrayDeque; From 5648438b66f3b182090d1119b72179c59044bbbf Mon Sep 17 00:00:00 2001 From: Skelebot Date: Wed, 21 Apr 2021 17:10:40 +0200 Subject: [PATCH 2/4] Format layout-macro/src/tests/mod.rs --- layout-macro/tests/mod.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/layout-macro/tests/mod.rs b/layout-macro/tests/mod.rs index d33d293..cc34dff 100644 --- a/layout-macro/tests/mod.rs +++ b/layout-macro/tests/mod.rs @@ -1,11 +1,10 @@ extern crate layout_macro; -use layout_macro::layout; -use keyberon::action::{Action, k, l, m, HoldTapConfig, Action::*}; +use keyberon::action::{k, l, m, Action, Action::*, HoldTapConfig}; use keyberon::key_code::KeyCode::*; +use layout_macro::layout; #[test] fn test_layout_equality() { - macro_rules! s { ($k:expr) => { m(&[LShift, $k]) @@ -63,11 +62,10 @@ fn test_nesting() { [{k(D)} [(5) [C {k(D)}]]] } }; - static B: keyberon::layout::Layers = &[ - &[ - &[k(D), Action::MultipleActions(&[Action::Layer(5), Action::MultipleActions(&[k(C), k(D)])])] - ] - ]; + static B: keyberon::layout::Layers = &[&[&[ + k(D), + Action::MultipleActions(&[Action::Layer(5), Action::MultipleActions(&[k(C), k(D)])]), + ]]]; assert_eq!(A, B); } From 231c806c4b58946d32cfc52101eb62d37beb34e4 Mon Sep 17 00:00:00 2001 From: Skelebot Date: Thu, 22 Apr 2021 13:49:22 +0200 Subject: [PATCH 3/4] Fix backslash behavior in literals --- layout-macro/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/layout-macro/src/lib.rs b/layout-macro/src/lib.rs index 5dfb370..85d56fc 100644 --- a/layout-macro/src/lib.rs +++ b/layout-macro/src/lib.rs @@ -164,12 +164,14 @@ fn literal_to_keycode(l: &Literal, out: &mut TokenStream) { // Char literals; mostly punctuation which can't be properly tokenized alone '\'' => { match repr.chars().nth(1).unwrap() { - '\\' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Bslash), }), + '\\' => match repr.chars().nth(2).unwrap() { + '\\' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Bslash), }), + '\'' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Quote), }), + _ => emit_error!(l, "Literal could not be parsed as a keycode"; help = "Maybe try without quotes?") + } '[' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::LBracket), }), ']' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::RBracket), }), - '`' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Grave), }), - '\'' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Quote), }), '"' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Quote]), }), '(' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb9]), }), ')' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb0]), }), From 22fbb77b10c2a73b8d2d6442d5735a03d87b11e8 Mon Sep 17 00:00:00 2001 From: Skelebot Date: Mon, 26 Apr 2021 17:23:27 +0200 Subject: [PATCH 4/4] Change macro crate name, update changelog, other improvements --- CHANGELOG.md | 11 +-- Cargo.toml | 2 +- {layout-macro => keyberon-macros}/Cargo.toml | 4 +- {layout-macro => keyberon-macros}/src/lib.rs | 73 +++++++++---------- .../tests/mod.rs | 15 +++- src/layout.rs | 2 +- 6 files changed, 56 insertions(+), 51 deletions(-) rename {layout-macro => keyberon-macros}/Cargo.toml (80%) rename {layout-macro => keyberon-macros}/src/lib.rs (67%) rename {layout-macro => keyberon-macros}/tests/mod.rs (90%) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfff902..69258cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,19 @@ * New Keyboard::leds_mut function for getting underlying leds object. * Made Layout::current_layer public for getting current active layer. +* Added a procedural macro for defining layouts (`keyberon::layout::layout`) Breaking changes: -* Update to generic_array 0.14, that is exposed in matrix. The update +* Update to generic_array 0.14, which is exposed in matrix. The update should be transparent. * `Action::HoldTap` now takes a configuration for different behaviors. * `Action::HoldTap` now takes the `tap_hold_interval` field. Not implemented yet. * `Action` is now generic, for the `Action::Custom(T)` variant, - allowing custom action to be handled outside of keyberon. This - functionality can be used to drive non keyboard actions, as reset - the microcontroller, drive leds (for backlight or underglow for - example), manage a mouse emulation, or any other ideas you can + allowing custom actions to be handled outside of keyberon. This + functionality can be used to drive non keyboard actions, such as resetting + the microcontroller, driving leds (for backlight or underglow for + example), managing a mouse emulation, or any other ideas you can have. As there is a default value for the type parameter, the update should be transparent. * Rename MeidaCoffee in MediaCoffee to fix typo. diff --git a/Cargo.toml b/Cargo.toml index e39d865..608a684 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" readme = "README.md" [dependencies] -layout-macro = { version = "0.1.0", path = "./layout-macro" } +keyberon-macros = { version = "0.1.0", path = "./keyberon-macros" } either = { version = "1.6", default-features = false } generic-array = "0.14" embedded-hal = { version = "0.2", features = ["unproven"] } diff --git a/layout-macro/Cargo.toml b/keyberon-macros/Cargo.toml similarity index 80% rename from layout-macro/Cargo.toml rename to keyberon-macros/Cargo.toml index d33c377..5bb1fa6 100644 --- a/layout-macro/Cargo.toml +++ b/keyberon-macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "layout-macro" +name = "keyberon-macros" version = "0.1.0" authors = ["Antoni Simka "] edition = "2018" @@ -13,4 +13,4 @@ proc-macro2 = "1.0" quote = "1.0" [dev-dependencies] -keyberon = { path = "../" } \ No newline at end of file +keyberon = { path = "../" } diff --git a/layout-macro/src/lib.rs b/keyberon-macros/src/lib.rs similarity index 67% rename from layout-macro/src/lib.rs rename to keyberon-macros/src/lib.rs index 85d56fc..b3288c5 100644 --- a/layout-macro/src/lib.rs +++ b/keyberon-macros/src/lib.rs @@ -144,49 +144,42 @@ fn punctuation_to_keycode(p: &Punct, out: &mut TokenStream) { } fn literal_to_keycode(l: &Literal, out: &mut TokenStream) { - let repr = l.to_string(); - match repr.chars().next().unwrap() { - '0'..='9' if repr.len() == 1 => { - match repr.chars().next().unwrap() { - '1' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb1), }), - '2' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb2), }), - '3' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb3), }), - '4' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb4), }), - '5' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb5), }), - '6' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb6), }), - '7' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb7), }), - '8' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb8), }), - '9' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb9), }), - '0' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb0), }), - _ => unreachable!() - } - } + //let repr = l.to_string(); + match l.to_string().as_str() { + "1" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb1), }), + "2" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb2), }), + "3" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb3), }), + "4" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb4), }), + "5" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb5), }), + "6" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb6), }), + "7" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb7), }), + "8" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb8), }), + "9" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb9), }), + "0" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Kb0), }), + // Char literals; mostly punctuation which can't be properly tokenized alone - '\'' => { - match repr.chars().nth(1).unwrap() { - '\\' => match repr.chars().nth(2).unwrap() { - '\\' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Bslash), }), - '\'' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Quote), }), - _ => emit_error!(l, "Literal could not be parsed as a keycode"; help = "Maybe try without quotes?") - } - '[' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::LBracket), }), - ']' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::RBracket), }), - '`' => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Grave), }), - '"' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Quote]), }), - '(' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb9]), }), - ')' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb0]), }), - '{' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::LBracket]), }), - '}' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::RBracket]), }), - '_' => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Minus]), }), - _ => emit_error!(l, "Literal could not be parsed as a keycode"; help = "Maybe try without quotes?") + r#"'\''"# => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Quote), }), + r#"'\\'"# => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Bslash), }), + // Shifted characters + "'['" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::LBracket), }), + "']'" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::RBracket), }), + "'`'" => out.extend(quote! { keyberon::action::Action::KeyCode(keyberon::key_code::KeyCode::Grave), }), + "'\"'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Quote]), }), + "'('" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb9]), }), + "')'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Kb0]), }), + "'{'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::LBracket]), }), + "'}'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::RBracket]), }), + "'_'" => out.extend(quote! { keyberon::action::Action::MultipleKeyCodes(&[keyberon::key_code::KeyCode::LShift, keyberon::key_code::KeyCode::Minus]), }), + + s if s.starts_with('\'') => emit_error!(l, "Literal could not be parsed as a keycode"; help = "Maybe try without quotes?"), + + s if s.starts_with('\"') => { + if s.len() == 3 { + emit_error!(l, "Typing strings on key press is not yet supported"; help = "Did you mean to use apostrophes instead of quotes?"); + } else { + emit_error!(l, "Typing strings on key press is not yet supported"); } } - '"' => if repr.len() == 3 { - emit_error!(l, "Typing strings on key press is not yet supported"; help = "Did you mean to use apostrophes instead of quotes?"); - } else { - emit_error!(l, "Typing strings on key press is not yet supported"); - } - // Is this reachable? _ => emit_error!(l, "Literal could not be parsed as a keycode") } } diff --git a/layout-macro/tests/mod.rs b/keyberon-macros/tests/mod.rs similarity index 90% rename from layout-macro/tests/mod.rs rename to keyberon-macros/tests/mod.rs index cc34dff..4ce3af8 100644 --- a/layout-macro/tests/mod.rs +++ b/keyberon-macros/tests/mod.rs @@ -1,7 +1,7 @@ -extern crate layout_macro; +extern crate keyberon_macros; use keyberon::action::{k, l, m, Action, Action::*, HoldTapConfig}; use keyberon::key_code::KeyCode::*; -use layout_macro::layout; +use keyberon_macros::layout; #[test] fn test_layout_equality() { @@ -77,3 +77,14 @@ fn test_layer_switch() { } }; } + +#[test] +fn test_escapes() { + static A: keyberon::layout::Layers = layout! { + { + ['\\' '\''] + } + }; + static B: keyberon::layout::Layers = &[&[&[k(Bslash), k(Quote)]]]; + assert_eq!(A, B); +} diff --git a/src/layout.rs b/src/layout.rs index 2556d7b..550609f 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -43,7 +43,7 @@ /// // ... /// }; /// ``` -pub use layout_macro::layout; +pub use keyberon_macros::*; use crate::action::{Action, HoldTapConfig}; use crate::key_code::KeyCode;