python_instruction_dsl_proc/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::parse_quote;
5use syn::{
6    Expr, Ident, LitInt, Token, bracketed, parenthesized, parse::Parse, parse_macro_input,
7    token::Paren,
8};
9
10enum StackItem {
11    Name(Ident),
12    NameCounted(Ident, Expr),
13    /// Amount of unused (and unnamed) stack items
14    Unused(Expr),
15}
16
17struct StackEffect {
18    pops: Vec<StackItem>,
19    pushes: Vec<StackItem>,
20}
21
22struct Opcode {
23    name: Ident,
24    number: LitInt,
25    stack_effect: StackEffect,
26}
27
28impl Parse for Opcode {
29    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
30        // Example: LOAD_CONST = 100 ( -- constant)
31        let name: Ident = input.parse()?;
32        input.parse::<Token![=]>()?;
33        let number: LitInt = input.parse()?;
34
35        let inner_stack_effect;
36
37        parenthesized!(inner_stack_effect in input);
38
39        let mut stack_effect = StackEffect {
40            pops: vec![],
41            pushes: vec![],
42        };
43
44        // Pops
45        while inner_stack_effect.peek(Ident) {
46            let name: Ident = inner_stack_effect.parse()?;
47
48            stack_effect.pops.push(
49                // This name is special, see the reference document on GitHub.
50                if name == "unused" {
51                    if inner_stack_effect.peek(syn::token::Bracket) {
52                        let inner_bracket;
53                        bracketed!(inner_bracket in inner_stack_effect);
54                        let size: Expr = inner_bracket.parse()?;
55                        StackItem::Unused(size)
56                    } else {
57                        StackItem::Unused(Expr::Lit(syn::ExprLit {
58                            attrs: vec![],
59                            lit: syn::Lit::Int(LitInt::new(
60                                "1",
61                                proc_macro::Span::call_site().into(),
62                            )),
63                        }))
64                    }
65                } else {
66                    if inner_stack_effect.peek(syn::token::Bracket) {
67                        let inner_bracket;
68                        bracketed!(inner_bracket in inner_stack_effect);
69                        let size: Expr = inner_bracket.parse()?;
70                        StackItem::NameCounted(name, size)
71                    } else {
72                        StackItem::Name(name)
73                    }
74                },
75            );
76
77            if inner_stack_effect.parse::<Token![,]>().is_err() {
78                break;
79            }
80        }
81
82        inner_stack_effect.parse::<Token![-]>()?;
83        inner_stack_effect.parse::<Token![-]>()?;
84
85        while inner_stack_effect.peek(Ident) {
86            let name: Ident = inner_stack_effect.parse()?;
87
88            stack_effect.pushes.push(
89                // This name is special, see the reference document on GitHub.
90                if name == "unused" {
91                    if inner_stack_effect.peek(syn::token::Bracket) {
92                        let inner_bracket;
93                        bracketed!(inner_bracket in inner_stack_effect);
94                        let size: Expr = inner_bracket.parse()?;
95                        StackItem::Unused(size)
96                    } else {
97                        StackItem::Unused(Expr::Lit(syn::ExprLit {
98                            attrs: vec![],
99                            lit: syn::Lit::Int(LitInt::new(
100                                "1",
101                                proc_macro::Span::call_site().into(),
102                            )),
103                        }))
104                    }
105                } else {
106                    if inner_stack_effect.peek(syn::token::Bracket) {
107                        let inner_bracket;
108                        bracketed!(inner_bracket in inner_stack_effect);
109                        let size: Expr = inner_bracket.parse()?;
110                        StackItem::NameCounted(name, size)
111                    } else {
112                        StackItem::Name(name)
113                    }
114                },
115            );
116
117            if inner_stack_effect.parse::<Token![,]>().is_err() {
118                break;
119            }
120        }
121
122        Ok(Opcode {
123            name,
124            number,
125            stack_effect,
126        })
127    }
128}
129
130struct Opcodes {
131    opcodes: Vec<Opcode>,
132}
133
134impl Parse for Opcodes {
135    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
136        let mut opcodes = vec![];
137
138        loop {
139            opcodes.push(Opcode::parse(input)?);
140
141            if input.parse::<Token![,]>().is_err() || input.is_empty() {
142                break;
143            }
144        }
145
146        Ok(Opcodes { opcodes })
147    }
148}
149
150fn sum_items(items: &[StackItem]) -> Expr {
151    if items.is_empty() {
152        // 0 if empty
153        Expr::Lit(syn::ExprLit {
154            attrs: vec![],
155            lit: syn::Lit::Int(LitInt::new("0", proc_macro::Span::call_site().into())),
156        })
157    } else {
158        items
159            .iter()
160            .map(|p| match p {
161                StackItem::Name(_) => Expr::Lit(syn::ExprLit {
162                    attrs: vec![],
163                    lit: syn::Lit::Int(LitInt::new("1", proc_macro::Span::call_site().into())),
164                }),
165                StackItem::NameCounted(_, size) => size.clone(),
166                StackItem::Unused(size) => size.clone(),
167            })
168            .reduce(|left, right| {
169                syn::Expr::Binary(syn::ExprBinary {
170                    attrs: vec![],
171                    left: Box::new(left),
172                    op: syn::BinOp::Add(syn::token::Plus {
173                        spans: [proc_macro::Span::call_site().into()],
174                    }),
175                    right: Box::new(right),
176                })
177            })
178            .expect("Something is wrong with the format")
179    }
180}
181
182#[proc_macro]
183pub fn define_opcodes(input: TokenStream) -> TokenStream {
184    let Opcodes { opcodes } = parse_macro_input!(input as Opcodes);
185
186    let names: Vec<_> = opcodes.iter().map(|o| &o.name).collect();
187    let numbers: Vec<_> = opcodes.iter().map(|o| &o.number).collect();
188
189    let pops: Vec<_> = opcodes
190        .iter()
191        .map(|o| sum_items(&o.stack_effect.pops))
192        .collect();
193
194    let pushes: Vec<_> = opcodes
195        .iter()
196        .map(|o| sum_items(&o.stack_effect.pushes))
197        .collect();
198
199    let expanded = quote! {
200        #[allow(non_camel_case_types)]
201        #[allow(clippy::upper_case_acronyms)]
202        #[derive(Debug, Clone, PartialEq, Eq)]
203        pub enum Opcode {
204            #( #names ),*,
205            INVALID_OPCODE(u8),
206        }
207
208        impl From<u8> for Opcode {
209            fn from(value: u8) -> Self {
210                match value {
211                    #( #numbers => Opcode::#names, )*
212                    _ => Opcode::INVALID_OPCODE(value),
213                }
214            }
215        }
216
217        impl From<Opcode> for u8 {
218            fn from(value: Opcode) -> Self {
219                match value {
220                    #( Opcode::#names => #numbers , )*
221                    Opcode::INVALID_OPCODE(value) => value,
222                }
223            }
224        }
225
226        impl From<(Opcode, u8)> for Instruction {
227            fn from(value: (Opcode, u8)) -> Self {
228                match value.0 {
229                    #(
230                        Opcode::#names => get_names!(@instruction #names, value.1),
231                    )*
232                    Opcode::INVALID_OPCODE(opcode) => {
233                        if !cfg!(test) {
234                            Instruction::InvalidOpcode((opcode, value.1))
235                        } else {
236                            panic!("Testing environment should not come across invalid opcodes")
237                        }
238                    },
239                }
240            }
241        }
242
243        impl Opcode {
244            pub fn from_instruction(instruction: &Instruction) -> Self {
245                match instruction {
246                    #(
247                        get_names!(@instruction #names) => Opcode::#names ,
248                    )*
249                    Instruction::InvalidOpcode((opcode, _)) => Opcode::INVALID_OPCODE(*opcode),
250                }
251            }
252        }
253
254        impl StackEffectTrait for Opcode {
255            fn stack_effect(&self, oparg: u32, jump: Option<bool>) -> StackEffect {
256                match &self {
257                    #(
258                        Opcode::#names => StackEffect { pops: #pops, pushes: #pushes },
259                    )*
260                }
261            }
262        }
263
264        #[macro_export]
265        macro_rules! get_names {
266            (@instruction $variant:ident, $val:expr) => {
267                paste::paste! { Instruction::[<$variant:camel>]($val) }
268            };
269            (@instruction $variant:ident) => {
270                paste::paste! { Instruction::[<$variant:camel>](_) }
271            };
272        }
273    };
274
275    expanded.into()
276}