python_instruction_dsl_proc/
lib.rs1extern 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 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 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 while inner_stack_effect.peek(Ident) {
45 let name: Ident = inner_stack_effect.parse()?;
46
47 stack_effect.pops.push(
48 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 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 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}