hlbc_derive/
lib.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{
4    Data, DeriveInput, Expr, ExprLit, GenericArgument, Ident, Lit, LitStr, PathArguments, Type,
5    Variant,
6};
7
8#[proc_macro_derive(OpcodeHelper)]
9pub fn derive_opcode_helper(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
10    let ast = syn::parse_macro_input!(input as DeriveInput);
11    let variants = match &ast.data {
12        Data::Enum(v) => Some(&v.variants),
13        _ => None,
14    }
15    .unwrap();
16
17    let name = &ast.ident;
18    let i = 0..variants.len() as u8;
19
20    let initr = variants.iter().map(|v| read_variant(name, v));
21    let initw = variants
22        .iter()
23        .enumerate()
24        .map(|(i, v)| write_variant(name, v, i as u8));
25    let vname = variants.iter().map(|v| &v.ident);
26    let vname2 = vname.clone();
27    let vname_str = variants
28        .iter()
29        .map(|v| LitStr::new(&v.ident.to_string(), v.ident.span()));
30    let vname_str2 = vname_str.clone();
31    let vdesc = variants.iter().map(|v| {
32        let mut acc = String::new();
33        for attr in &v.attrs {
34            if let Ok(nv) = attr.meta.require_name_value() {
35                if nv.path.is_ident("doc") {
36                    match &nv.value {
37                        Expr::Lit(ExprLit {
38                            lit: Lit::Str(lit), ..
39                        }) => {
40                            let lstr = lit.value();
41                            let to_acc = lstr.trim();
42                            if !to_acc.is_empty() {
43                                acc.push_str(to_acc);
44                                acc.push('\n');
45                            }
46                        }
47                        _ => {}
48                    }
49                }
50            }
51        }
52        acc.trim().to_string()
53    });
54    let vdefault_init = variants.iter().map(|v| {
55        let vname = &v.ident;
56        let finit = v.fields.iter().map(|f| {
57            let fname = f.ident.as_ref().unwrap();
58            quote! {
59                #fname: Default::default()
60            }
61        });
62        quote! {
63            #name::#vname { #( #finit,)* }
64        }
65    });
66
67    proc_macro::TokenStream::from(quote! {
68        impl #name {
69            /// Decode an instruction
70            pub fn read(r: &mut impl std::io::Read) -> crate::Result<#name> {
71
72                use byteorder::ReadBytesExt;
73                use crate::types::*;
74                use crate::read::{read_vari, read_varu};
75
76                let op = r.read_u8()?;
77                match op {
78                    #( #i => #initr, )*
79                    other => Err(crate::Error::MalformedBytecode(format!("Unknown opcode {}", op))),
80                }
81            }
82
83            /// Encode an instruction
84            pub fn write(&self, w: &mut impl std::io::Write) -> crate::Result<()> {
85
86                use byteorder::WriteBytesExt;
87                use crate::types::*;
88                use crate::write::write_var;
89
90                match self {
91                    #( #initw )*
92                }
93
94                Ok(())
95            }
96
97            /// Get the opcode name
98            pub fn name(&self) -> &'static str {
99                match self {
100                    #( #name::#vname { .. } => #vname_str, )*
101                }
102            }
103
104            /// Get the opcode description
105            pub fn description(&self) -> &'static str {
106                match self {
107                    #( #name::#vname2 { .. } => #vdesc, )*
108                }
109            }
110
111            /// Get an opcode from its name. Returns a default value for the variant.
112            pub fn from_name(name: &str) -> Option<Self> {
113                match name {
114                    #( #vname_str2 => Some(#vdefault_init), )*
115                    _ => None
116                }
117            }
118        }
119    })
120}
121
122/// Print a type to string
123fn ident(ty: &Type) -> String {
124    match ty {
125        Type::Path(path) => {
126            let seg = &path.path.segments[0];
127            match &seg.arguments {
128                PathArguments::None => seg.ident.to_string(),
129                PathArguments::AngleBracketed(a) => {
130                    let a = match &a.args[0] {
131                        GenericArgument::Type(ty) => ident(ty),
132                        _ => unreachable!(),
133                    };
134                    format!("{}<{}>", seg.ident, a)
135                }
136                _ => unreachable!(),
137            }
138        }
139        other => unreachable!("unknown type {:?}", other),
140    }
141}
142
143fn read_variant(enum_name: &Ident, v: &Variant) -> TokenStream {
144    let rvi32 = quote!(read_vari(r)?);
145    let rvu32 = quote!(read_varu(r)?);
146    let reg = quote!(Reg(#rvi32 as u32));
147
148    let vname = &v.ident;
149    let fname = v.fields.iter().map(|f| &f.ident);
150    let fvalue = v.fields.iter().map(|f| match ident(&f.ty).as_str() {
151        "usize" => quote!(#rvi32 as usize),
152        "i32" => quote! {
153            #rvi32 as JumpOffset
154        },
155        "JumpOffset" => quote! {
156            #rvi32 as JumpOffset
157        },
158        "Vec<JumpOffset>" => quote! {
159            {
160                let n = #rvu32 as usize;
161                let mut offsets = Vec::with_capacity(n);
162                for _ in 0..n {
163                    offsets.push(#rvu32 as JumpOffset);
164                }
165                offsets
166            }
167        },
168        "Reg" => reg.clone(),
169        "Vec<Reg>" => quote! {
170            {
171                let n = r.read_u8()? as usize;
172                let mut regs = Vec::with_capacity(n);
173                for _ in 0..n {
174                    regs.push(#reg);
175                }
176                regs
177            }
178        },
179        "RefInt" => quote! {
180            RefInt::read(r)?
181        },
182        "RefFloat" => quote! {
183            RefFloat::read(r)?
184        },
185        "RefBytes" => quote! {
186            RefBytes(#rvi32 as usize)
187        },
188        "RefString" => quote! {
189            RefString::read(r)?
190        },
191        "RefType" => quote! {
192            RefType::read(r)?
193        },
194        "ValBool" => quote! {
195            ValBool(#rvi32 == 1)
196        },
197        "RefFun" => quote! {
198            RefFun::read(r)?
199        },
200        "RefField" => quote! {
201            RefField::read(r)?
202        },
203        "RefGlobal" => quote! {
204            RefGlobal::read(r)?
205        },
206        "RefEnumConstruct" => quote! {
207            RefEnumConstruct(#rvi32 as usize)
208        },
209        _ => TokenStream::default(),
210    });
211    quote! {
212        Ok(#enum_name::#vname {
213            #( #fname: #fvalue, )*
214        })
215    }
216}
217
218fn write_variant(enum_name: &Ident, v: &Variant, i: u8) -> TokenStream {
219    let vname = &v.ident;
220    let fname = v.fields.iter().map(|f| &f.ident);
221    let fwrite = v.fields.iter().map(|f| {
222        let fname = f.ident.as_ref().unwrap();
223        match ident(&f.ty).as_str() {
224            "usize" => quote!(write_var(w, #fname as i32)?;),
225            "i32" => quote! {
226                write_var(w, #fname)?;
227            },
228            "JumpOffset" => quote! {
229                write_var(w, *#fname as i32)?;
230            },
231            "Vec<JumpOffset>" => quote! {
232                {
233                    write_var(w, #fname.len() as i32)?;
234                    for r__ in #fname {
235                        write_var(w, *r__ as i32)?;
236                    }
237                }
238            },
239            "Reg" => quote! {
240                write_var(w, #fname.0 as i32)?;
241            },
242            "Vec<Reg>" => quote! {
243                {
244                    w.write_u8(#fname.len() as u8)?;
245                    for r__ in #fname {
246                        write_var(w, r__.0 as i32)?;
247                    }
248                }
249            },
250            "RefInt" | "RefFloat" | "RefString" | "RefType" | "RefFun" | "RefField"
251            | "RefGlobal" => quote! {
252                #fname.write(w)?;
253            },
254            "RefBytes" => quote! {
255                write_var(w, #fname.0 as i32)?;
256            },
257            "ValBool" => quote! {
258                write_var(w, if #fname.0 { 1 } else { 0 })?;
259            },
260            "RefEnumConstruct" => quote! {
261                write_var(w, #fname.0 as i32)?;
262            },
263            _ => TokenStream::default(),
264        }
265    });
266    quote! {
267        #enum_name::#vname { #( #fname, )* } => {
268            w.write_u8(#i)?;
269            #( #fwrite )*
270        }
271    }
272}