1extern crate proc_macro;
11
12use proc_macro::TokenStream;
13use proc_macro2::TokenStream as TokenStream2;
14use quote::{format_ident, quote};
15use syn::parse::{Parse, ParseStream};
16use syn::punctuated::Punctuated;
17use syn::{braced, bracketed, parenthesized, parse_macro_input, Expr, Ident, LitStr, Token};
18
19struct OpcodeEntry {
40 variant: Ident,
41 dispatch: Dispatch,
42 disasm: Disasm,
43 flags: Vec<Ident>,
44}
45
46enum Dispatch {
47 Sync(Expr),
48 SyncVoid(Expr),
49 SyncReturn(Expr),
50 Split { sync: Expr, async_: Expr },
51 Async(Expr),
52}
53
54struct Disasm {
55 helper: Ident,
56 label: LitStr,
57}
58
59impl Parse for OpcodeEntry {
60 fn parse(input: ParseStream) -> syn::Result<Self> {
61 let variant: Ident = input.parse()?;
62 let body;
63 braced!(body in input);
64
65 let kind: Ident = body.parse()?;
66 let kind_args;
67 parenthesized!(kind_args in body);
68
69 let dispatch = match kind.to_string().as_str() {
70 "sync" => {
71 let expr: Expr = kind_args.parse()?;
72 Dispatch::Sync(expr)
73 }
74 "sync_void" => {
75 let expr: Expr = kind_args.parse()?;
76 Dispatch::SyncVoid(expr)
77 }
78 "sync_return" => {
79 let expr: Expr = kind_args.parse()?;
80 Dispatch::SyncReturn(expr)
81 }
82 "split" => {
83 let sync: Expr = kind_args.parse()?;
84 kind_args.parse::<Token![,]>()?;
85 let async_: Expr = kind_args.parse()?;
86 Dispatch::Split { sync, async_ }
87 }
88 "async_op" => {
89 let expr: Expr = kind_args.parse()?;
90 Dispatch::Async(expr)
91 }
92 other => {
93 return Err(syn::Error::new(
94 kind.span(),
95 format!(
96 "unknown opcode kind `{other}` — expected one of: \
97 sync, sync_void, sync_return, split, async_op"
98 ),
99 ));
100 }
101 };
102
103 body.parse::<Token![,]>()?;
104 let disasm_kw: Ident = body.parse()?;
105 if disasm_kw != "disasm" {
106 return Err(syn::Error::new(
107 disasm_kw.span(),
108 "expected `disasm:` after dispatch kind",
109 ));
110 }
111 body.parse::<Token![:]>()?;
112 let helper: Ident = body.parse()?;
113 let helper_args;
114 parenthesized!(helper_args in body);
115 let label: LitStr = helper_args.parse()?;
116 let disasm = Disasm { helper, label };
117
118 let mut flags = Vec::new();
119 if body.peek(Token![,]) {
120 body.parse::<Token![,]>()?;
121 if !body.is_empty() {
122 let flags_kw: Ident = body.parse()?;
123 if flags_kw != "flags" {
124 return Err(syn::Error::new(
125 flags_kw.span(),
126 "expected `flags: [...]` after `disasm`",
127 ));
128 }
129 body.parse::<Token![:]>()?;
130 let flags_inner;
131 bracketed!(flags_inner in body);
132 let parsed: Punctuated<Ident, Token![,]> =
133 Punctuated::parse_terminated(&flags_inner)?;
134 flags = parsed.into_iter().collect();
135 }
136 }
137
138 Ok(Self {
139 variant,
140 dispatch,
141 disasm,
142 flags,
143 })
144 }
145}
146
147struct Opcodes {
148 entries: Vec<OpcodeEntry>,
149}
150
151impl Parse for Opcodes {
152 fn parse(input: ParseStream) -> syn::Result<Self> {
153 let mut entries = Vec::new();
154 while !input.is_empty() {
155 let entry: OpcodeEntry = input.parse()?;
156 input.parse::<Token![;]>()?;
157 entries.push(entry);
158 }
159 Ok(Self { entries })
160 }
161}
162
163#[proc_macro]
175pub fn define_opcodes(input: TokenStream) -> TokenStream {
176 let opcodes = parse_macro_input!(input as Opcodes);
177 match expand(&opcodes) {
178 Ok(ts) => ts.into(),
179 Err(e) => e.to_compile_error().into(),
180 }
181}
182
183fn expand(opcodes: &Opcodes) -> syn::Result<TokenStream2> {
184 if opcodes.entries.is_empty() {
185 return Err(syn::Error::new(
186 proc_macro2::Span::call_site(),
187 "define_opcodes! requires at least one opcode entry",
188 ));
189 }
190
191 let sync_arms = opcodes.entries.iter().map(|e| {
193 let v = &e.variant;
194 match &e.dispatch {
195 Dispatch::Sync(expr) => quote!(Op::#v => #expr,),
196 Dispatch::SyncVoid(expr) => quote!(Op::#v => { #expr; Ok(()) },),
197 Dispatch::SyncReturn(expr) => quote!(Op::#v => return ::core::option::Option::Some(::core::result::Result::Err(#expr)),),
198 Dispatch::Split { sync, .. } => quote!(Op::#v => return #sync,),
199 Dispatch::Async(_) => quote!(Op::#v => return ::core::option::Option::None,),
200 }
201 });
202
203 let async_arms = opcodes.entries.iter().filter_map(|e| {
208 let v = &e.variant;
209 match &e.dispatch {
210 Dispatch::Async(expr) => Some(quote!(Op::#v => #expr,)),
211 Dispatch::Split { async_, .. } => Some(quote!(Op::#v => #async_,)),
212 _ => None,
213 }
214 });
215
216 let disasm_arms = opcodes.entries.iter().map(|e| {
222 let v = &e.variant;
223 let helper = format_ident!("disasm_{}", e.disasm.helper);
224 let label = &e.disasm.label;
225 quote!(Op::#v => crate::chunk::#helper(self, ip, #label),)
226 });
227
228 let mut flag_groups: std::collections::BTreeMap<String, Vec<&Ident>> =
232 std::collections::BTreeMap::new();
233 for entry in &opcodes.entries {
234 for flag in &entry.flags {
235 flag_groups
236 .entry(flag.to_string())
237 .or_default()
238 .push(&entry.variant);
239 }
240 }
241
242 let flag_fns = flag_groups.iter().map(|(name, variants)| {
247 let fn_ident = match name.as_str() {
248 "reads_outer_name" => format_ident!("op_reads_outer_name"),
249 "adaptive_binary" => format_ident!("is_adaptive_binary_op"),
250 other => format_ident!("op_has_flag_{}", other),
251 };
252 quote!(
253 #[inline]
254 pub(crate) fn #fn_ident(op: Op) -> bool {
255 matches!(op, #(Op::#variants)|*)
256 }
257 )
258 });
259
260 let out = quote! {
261 impl crate::vm::Vm {
262 pub(super) fn execute_op_sync(&mut self, op: Op) -> ::core::option::Option<::core::result::Result<(), crate::value::VmError>> {
268 let result: ::core::result::Result<(), crate::value::VmError> = match op {
269 #(#sync_arms)*
270 };
271 ::core::option::Option::Some(result)
272 }
273
274 pub(super) fn execute_op_async(
278 &mut self,
279 op: Op,
280 ) -> impl ::core::future::Future<
281 Output = ::core::result::Result<(), crate::value::VmError>,
282 > + ::core::marker::Send
283 + '_ {
284 OpcodeDispatchFuture::new(async move {
285 match op {
286 #(#async_arms)*
287 sync_op => {
288 debug_assert!(
289 false,
290 "execute_op_async called with sync opcode {sync_op:?} \
291 — define_opcodes! kept the two halves aligned"
292 );
293 ::core::result::Result::Err(crate::value::VmError::Runtime(format!(
294 "internal VM dispatch error: {sync_op:?} is not an async opcode"
295 )))
296 }
297 }
298 })
299 }
300 }
301
302 impl crate::chunk::Chunk {
303 #[allow(clippy::too_many_lines)]
309 pub(crate) fn disassemble_op(&self, op: Op, ip: &mut usize, out: &mut String) {
310 let line = match op {
311 #(#disasm_arms)*
312 };
313 out.push_str(&line);
314 out.push('\n');
315 }
316 }
317
318 #(#flag_fns)*
319 };
320
321 Ok(out)
322}