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