vte_generate_state_changes/
lib.rs

1#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)]
2
3extern crate proc_macro;
4
5use std::iter::Peekable;
6
7use proc_macro2::TokenTree::{Group, Literal, Punct};
8use proc_macro2::{token_stream, TokenStream, TokenTree};
9use quote::quote;
10
11/// Create a `const fn` which will return an array with all state changes.
12#[proc_macro]
13pub fn generate_state_changes(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
14    // Convert from proc_macro -> proc_macro2
15    let item: TokenStream = item.into();
16    let mut iter = item.into_iter().peekable();
17
18    // Determine output function name
19    let fn_name = iter.next().unwrap();
20
21    // Separator between name and body with state changes
22    expect_punct(&mut iter, ',');
23
24    // Create token stream to assign each state change to the array
25    let assignments_stream = states_stream(&mut iter);
26
27    quote!(
28        const fn #fn_name() -> [[u8; 256]; 16] {
29            let mut state_changes = [[0; 256]; 16];
30
31            #assignments_stream
32
33            state_changes
34        }
35    )
36    .into()
37}
38
39/// Generate the array assignment statements for all origin states.
40fn states_stream(iter: &mut impl Iterator<Item = TokenTree>) -> TokenStream {
41    let mut states_stream = next_group(iter).into_iter().peekable();
42
43    // Loop over all origin state entries
44    let mut tokens = quote!();
45    while states_stream.peek().is_some() {
46        // Add all mappings for this state
47        tokens.extend(state_entry_stream(&mut states_stream));
48
49        // Allow trailing comma
50        optional_punct(&mut states_stream, ',');
51    }
52    tokens
53}
54
55/// Generate the array assignment statements for one origin state.
56fn state_entry_stream(iter: &mut Peekable<token_stream::IntoIter>) -> TokenStream {
57    // Origin state name
58    let state = iter.next().unwrap();
59
60    // Token stream with all the byte->target mappings
61    let mut changes_stream = next_group(iter).into_iter().peekable();
62
63    let mut tokens = quote!();
64    while changes_stream.peek().is_some() {
65        // Add next mapping for this state
66        tokens.extend(change_stream(&mut changes_stream, &state));
67
68        // Allow trailing comma
69        optional_punct(&mut changes_stream, ',');
70    }
71    tokens
72}
73
74/// Generate the array assignment statement for a single byte->target mapping for one state.
75fn change_stream(iter: &mut Peekable<token_stream::IntoIter>, state: &TokenTree) -> TokenStream {
76    // Start of input byte range
77    let start = next_usize(iter);
78
79    // End of input byte range
80    let end = if optional_punct(iter, '.') {
81        // Read inclusive end of range
82        expect_punct(iter, '.');
83        expect_punct(iter, '=');
84        next_usize(iter)
85    } else {
86        // Without range, end is equal to start
87        start
88    };
89
90    // Separator between byte input range and output state
91    expect_punct(iter, '=');
92    expect_punct(iter, '>');
93
94    // Token stream with target state and action
95    let mut target_change_stream = next_group(iter).into_iter().peekable();
96
97    let mut tokens = quote!();
98    while target_change_stream.peek().is_some() {
99        // Target state/action for all bytes in the range
100        let (target_state, target_action) = target_change(&mut target_change_stream);
101
102        // Create a new entry for every byte in the range
103        for byte in start..=end {
104            // TODO: Force adding `State::` and `Action::`?
105            // TODO: Should we really use `pack` here without import?
106            tokens.extend(quote!(
107                state_changes[State::#state as usize][#byte] =
108                    pack(State::#target_state, Action::#target_action);
109            ));
110        }
111    }
112    tokens
113}
114
115/// Get next target state and action.
116fn target_change(iter: &mut Peekable<token_stream::IntoIter>) -> (TokenTree, TokenTree) {
117    let target_state = iter.next().unwrap();
118
119    // Separator between state and action
120    expect_punct(iter, ',');
121
122    let target_action = iter.next().unwrap();
123
124    (target_state, target_action)
125}
126
127/// Check if next token matches specific punctuation.
128fn optional_punct(iter: &mut Peekable<token_stream::IntoIter>, c: char) -> bool {
129    match iter.peek() {
130        Some(Punct(punct)) if punct.as_char() == c => iter.next().is_some(),
131        _ => false,
132    }
133}
134
135/// Ensure next token matches specific punctuation.
136///
137/// # Panics
138///
139/// Panics if the punctuation does not match.
140fn expect_punct(iter: &mut impl Iterator<Item = TokenTree>, c: char) {
141    match iter.next() {
142        Some(Punct(ref punct)) if punct.as_char() == c => (),
143        token => panic!("Expected punctuation '{}', but got {:?}", c, token),
144    }
145}
146
147/// Get next token as [`usize`].
148///
149/// # Panics
150///
151/// Panics if the next token is not a [`usize`] in hex or decimal literal format.
152fn next_usize(iter: &mut impl Iterator<Item = TokenTree>) -> usize {
153    match iter.next() {
154        Some(Literal(literal)) => {
155            let literal = literal.to_string();
156            if let Some(prefix) = literal.strip_prefix("0x") {
157                usize::from_str_radix(prefix, 16).unwrap()
158            } else {
159                literal.parse::<usize>().unwrap()
160            }
161        },
162        token => panic!("Expected literal, but got {:?}", token),
163    }
164}
165
166/// Get next token as [`Group`].
167///
168/// # Panics
169///
170/// Panics if the next token is not a [`Group`].
171fn next_group(iter: &mut impl Iterator<Item = TokenTree>) -> TokenStream {
172    match iter.next() {
173        Some(Group(group)) => group.stream(),
174        token => panic!("Expected group, but got {:?}", token),
175    }
176}