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 NameCounted(Ident, Expr),
13 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 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 while inner_stack_effect.peek(Ident) {
46 let name: Ident = inner_stack_effect.parse()?;
47
48 stack_effect.pops.push(
49 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 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 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}